From 6de425ea5ebd8bba39b5e29a455838617709997b Mon Sep 17 00:00:00 2001 From: Saif Al Nuaimi Date: Fri, 28 Feb 2020 22:13:52 +0200 Subject: [PATCH 01/18] feat: commit --- APP/.gitignore | 31 ++ APP/.mvn/wrapper/MavenWrapperDownloader.java | 118 +++++++ APP/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes APP/.mvn/wrapper/maven-wrapper.properties | 2 + APP/Dockerfile | 13 + APP/docker-compose.yml | 21 ++ APP/local.dockerfile | 14 + APP/mvnw | 310 ++++++++++++++++++ APP/mvnw.cmd | 182 ++++++++++ APP/pom.xml | 66 ++++ .../java/com/webapp/app/AppApplication.java | 13 + .../com/webapp/app/config/RedisConfig.java | 30 ++ .../webapp/app/controller/CounterServlet.java | 56 ++++ .../main/java/com/webapp/app/model/User.java | 41 +++ .../app/repository/RedisRepository.java | 18 + .../app/repository/RedisRepositoryImpl.java | 49 +++ APP/src/main/resources/application.properties | 3 + APP/src/main/resources/static/main.js | 138 ++++++++ APP/src/main/resources/templates/index.html | 43 +++ .../com/webapp/app/AppApplicationTests.java | 13 + docs/README.md | 2 +- scripts/builder.sh | 42 +++ 22 files changed, 1204 insertions(+), 1 deletion(-) create mode 100644 APP/.gitignore create mode 100644 APP/.mvn/wrapper/MavenWrapperDownloader.java create mode 100644 APP/.mvn/wrapper/maven-wrapper.jar create mode 100644 APP/.mvn/wrapper/maven-wrapper.properties create mode 100644 APP/Dockerfile create mode 100644 APP/docker-compose.yml create mode 100644 APP/local.dockerfile create mode 100755 APP/mvnw create mode 100644 APP/mvnw.cmd create mode 100644 APP/pom.xml create mode 100644 APP/src/main/java/com/webapp/app/AppApplication.java create mode 100644 APP/src/main/java/com/webapp/app/config/RedisConfig.java create mode 100644 APP/src/main/java/com/webapp/app/controller/CounterServlet.java create mode 100644 APP/src/main/java/com/webapp/app/model/User.java create mode 100755 APP/src/main/java/com/webapp/app/repository/RedisRepository.java create mode 100755 APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java create mode 100644 APP/src/main/resources/application.properties create mode 100755 APP/src/main/resources/static/main.js create mode 100755 APP/src/main/resources/templates/index.html create mode 100644 APP/src/test/java/com/webapp/app/AppApplicationTests.java create mode 100755 scripts/builder.sh diff --git a/APP/.gitignore b/APP/.gitignore new file mode 100644 index 0000000..a2a3040 --- /dev/null +++ b/APP/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/APP/.mvn/wrapper/MavenWrapperDownloader.java b/APP/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..a45eb6b --- /dev/null +++ b/APP/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/APP/.mvn/wrapper/maven-wrapper.jar b/APP/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 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 EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/APP/.mvn/wrapper/maven-wrapper.properties b/APP/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..642d572 --- /dev/null +++ b/APP/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/APP/Dockerfile b/APP/Dockerfile new file mode 100644 index 0000000..9f22648 --- /dev/null +++ b/APP/Dockerfile @@ -0,0 +1,13 @@ +FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 +EXPOSE 8080 +WORKDIR /myapp +COPY APP/src /myapp/src +COPY APP/pom.xml /myapp/pom.xml +RUN mvn clean install + +FROM openjdk:8-jre-alpine as mvn_runner_stage2 +WORKDIR /myrunningapp +ARG hostname=saifnuaimii/app:v1.0.0 +EXPOSE 8080 +COPY --from=mvn_builder_stage1 /myapp . +CMD java $JAVA_OPTS -D -jar target/app-*.jar \ No newline at end of file diff --git a/APP/docker-compose.yml b/APP/docker-compose.yml new file mode 100644 index 0000000..83ab976 --- /dev/null +++ b/APP/docker-compose.yml @@ -0,0 +1,21 @@ +version: '2' +services: + webapp: + build: + context: . + dockerfile: local.dockerfile + ports: + - 8080:8080 + environment: + - SPRING_REDIS_HOST=redis-db + depends_on: + - redis-db + links: + - redis-db + + #---------------------------------- + redis-db: + restart: always + image: redis + ports: + - "6379:6379" \ No newline at end of file diff --git a/APP/local.dockerfile b/APP/local.dockerfile new file mode 100644 index 0000000..0c92d5e --- /dev/null +++ b/APP/local.dockerfile @@ -0,0 +1,14 @@ +FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 +EXPOSE 8080 +RUN apk add redis +WORKDIR /myapp +COPY src /myapp/src +COPY pom.xml /myapp/pom.xml +RUN mvn clean install + +FROM openjdk:8-jre-alpine as mvn_runner_stage2 +WORKDIR /myrunningapp +ARG hostname=saifnuaimii/app:v1.0.0 +EXPOSE 8080 +COPY --from=mvn_builder_stage1 /myapp . +CMD java $JAVA_OPTS -D -jar target/app-*.jar \ No newline at end of file diff --git a/APP/mvnw b/APP/mvnw new file mode 100755 index 0000000..a16b543 --- /dev/null +++ b/APP/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/APP/mvnw.cmd b/APP/mvnw.cmd new file mode 100644 index 0000000..c8d4337 --- /dev/null +++ b/APP/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/APP/pom.xml b/APP/pom.xml new file mode 100644 index 0000000..9fafa3f --- /dev/null +++ b/APP/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.4.RELEASE + + + com.webapp + app + 1.0.0-RELEASE + APP + Demo project for Spring Boot + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-data-redis + + + io.lettuce + lettuce-core + + + + + redis.clients + jedis + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/APP/src/main/java/com/webapp/app/AppApplication.java b/APP/src/main/java/com/webapp/app/AppApplication.java new file mode 100644 index 0000000..1b16909 --- /dev/null +++ b/APP/src/main/java/com/webapp/app/AppApplication.java @@ -0,0 +1,13 @@ +package com.webapp.app; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class AppApplication { + + public static void main(String[] args) { + SpringApplication.run(AppApplication.class, args); + } + +} diff --git a/APP/src/main/java/com/webapp/app/config/RedisConfig.java b/APP/src/main/java/com/webapp/app/config/RedisConfig.java new file mode 100644 index 0000000..12621a5 --- /dev/null +++ b/APP/src/main/java/com/webapp/app/config/RedisConfig.java @@ -0,0 +1,30 @@ +package com.webapp.app.config; + + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.listener.ChannelTopic; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.data.redis.serializer.GenericToStringSerializer; + +@Configuration +public class RedisConfig { + + @Bean + JedisConnectionFactory jedisConnectionFactory() { + JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(); + jedisConnectionFactory.getPoolConfig().setMaxIdle(30); + jedisConnectionFactory.getPoolConfig().setMinIdle(10); + return jedisConnectionFactory; + } + + @Bean + public RedisTemplate redisTemplate() { + final RedisTemplate template = new RedisTemplate(); + template.setConnectionFactory(jedisConnectionFactory()); + template.setValueSerializer(new GenericToStringSerializer(Object.class)); + return template; + } +} \ No newline at end of file diff --git a/APP/src/main/java/com/webapp/app/controller/CounterServlet.java b/APP/src/main/java/com/webapp/app/controller/CounterServlet.java new file mode 100644 index 0000000..0a67643 --- /dev/null +++ b/APP/src/main/java/com/webapp/app/controller/CounterServlet.java @@ -0,0 +1,56 @@ +package com.webapp.app.controller; + +import com.webapp.app.model.User; +import com.webapp.app.repository.RedisRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Objects; + + +@RestController +public class CounterServlet extends HttpServlet { + @Autowired + private RedisRepository redisRepository; + private int iHitCounter; + + public void init() throws ServletException { + iHitCounter = 0; + } + + @GetMapping("/") + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + String remoteAddr = ""; + + if (request != null) { + remoteAddr = request.getHeader("X-FORWARDED-FOR"); + if (remoteAddr == null || "".equals(remoteAddr)) { + remoteAddr = request.getRemoteAddr(); + } + User current = redisRepository.findUser(remoteAddr); + User user = new User(remoteAddr, Objects.nonNull(current) ? current.getCount() + 1 : 1); + redisRepository.add(user); + + PrintWriter out = response.getWriter(); + out.println("Hello World !"); + out.println("

Welcome to my website !


"); + out.println("You are visitor number: " + (++iHitCounter)); + out.println("IP number: " + (remoteAddr)); + out.println("count : " + (user.getCount())); + } + + } + + + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + doGet(request, response); + } +} \ No newline at end of file diff --git a/APP/src/main/java/com/webapp/app/model/User.java b/APP/src/main/java/com/webapp/app/model/User.java new file mode 100644 index 0000000..5829c9b --- /dev/null +++ b/APP/src/main/java/com/webapp/app/model/User.java @@ -0,0 +1,41 @@ +package com.webapp.app.model; + +import java.io.Serializable; + +public class User implements Serializable { + + + private static final long serialVersionUID = 1L; + private String id; + private int count; + + public User(String id, int count) { + this.id = id; + this.count = count; + } + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + @Override + public String toString() { + return "User{" + + "id='" + id + '\'' + + ", count=" + count + + '}'; + } +} diff --git a/APP/src/main/java/com/webapp/app/repository/RedisRepository.java b/APP/src/main/java/com/webapp/app/repository/RedisRepository.java new file mode 100755 index 0000000..3186cd3 --- /dev/null +++ b/APP/src/main/java/com/webapp/app/repository/RedisRepository.java @@ -0,0 +1,18 @@ +package com.webapp.app.repository; + + +import com.webapp.app.model.User; + +import java.util.Map; + +public interface RedisRepository { + + Map findAllUsers(); + + void add(User user); + + void delete(String id); + + User findUser(String id); + +} diff --git a/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java b/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java new file mode 100755 index 0000000..02ea8a1 --- /dev/null +++ b/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java @@ -0,0 +1,49 @@ +package com.webapp.app.repository; + +import com.webapp.app.model.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Repository; + +import javax.annotation.PostConstruct; +import java.util.Map; + +@Repository +public class RedisRepositoryImpl implements RedisRepository { + private static final String KEY = "User"; + + private RedisTemplate redisTemplate; + private HashOperations hashOperations; + + @Autowired + public RedisRepositoryImpl(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + @PostConstruct + private void init() { + hashOperations = redisTemplate.opsForHash(); + } + + public void add(final User user) { + hashOperations.put(KEY, user.getId(), user); + } + + public void delete(final String id) { + hashOperations.delete(KEY, id); + } + + public User findUser(final String id) { + Object obj = hashOperations.get(KEY, id); + if (obj instanceof User) + return (User) obj; + return new User("", 0); + } + + public Map findAllUsers() { + return hashOperations.entries(KEY); + } + + +} diff --git a/APP/src/main/resources/application.properties b/APP/src/main/resources/application.properties new file mode 100644 index 0000000..9104b8c --- /dev/null +++ b/APP/src/main/resources/application.properties @@ -0,0 +1,3 @@ +spring.redis.host=localhost +spring.redis.port=6379 +server.port=8080 \ No newline at end of file diff --git a/APP/src/main/resources/static/main.js b/APP/src/main/resources/static/main.js new file mode 100755 index 0000000..08c6332 --- /dev/null +++ b/APP/src/main/resources/static/main.js @@ -0,0 +1,138 @@ +/** + * Create HTML table row. + * + * \param text (str) HTML code to be placed inside the row. + */ +function tr(text) { + return '' + text + ''; +} + +/** + * Create HTML table cell element. + * + * \param text (str) The text to be placed inside the cell. + */ +function td(text) { + return '' + text + ''; +} + +/** + * Edit value: load key and value into inputs. + */ +function editButton(key) { + var format = ''; + return format.replace(/{key}/g, key); +} + +/** + * Create HTML button with which a key-value pair is deleted from persistence. + * + * \param key (str) that'll be deleted when the created button is clicked. + */ +function deleteButton(key) { + var format = '' + return format.replace(/{key}/g, key); +} + +/** + * Create HTML table row element. + * + * \param key (str) text into the key cell. + * \param value (str) text into the value cell. + */ +function row(key, value) { + return $( + tr( + td(key) + + td(value) + + td(editButton(key)) + + td(deleteButton(key)))); +} + +/** + * Clear and reload the values in data table. + */ +function refreshTable() { + $.get('/values', function(data) { + var attr, + mainTable = $('#mainTable tbody'); + mainTable.empty(); + for (attr in data) { + if (data.hasOwnProperty(attr)) { + mainTable.append(row(attr, data[attr])); + } + } + }); +} + +function editKey(key) { + /* Find the row with key in first column (key column). */ + var format = '#mainTable tbody td:first-child:contains("{key}")', + selector = format.replace(/{key}/, key), + cells = $(selector).parent().children(), + key = cells[0].textContent, + value = cells[1].textContent, + keyInput = $('#keyInput'), + valueInput = $('#valueInput'); + + /* Load the key and value texts into inputs + * Select value text so it can be directly typed to + */ + keyInput.val(key); + valueInput.val(value); + valueInput.select(); +} + +/** + * Delete key-value pair. + * + * \param key (str) The key to be deleted. + */ +function deleteKey(key) { + /* + * Delete the key. + * Reload keys and values in table to reflect the deleted ones. + * Set keyboard focus to key input: ready to start typing. + */ + $.post('/delete', {key: key}, function() { + refreshTable(); + $('#keyInput').focus(); + }); +} + +$(document).ready(function() { + var keyInput = $('#keyInput'), + valueInput = $('#valueInput'); + + refreshTable(); + $('#addForm').on('submit', function(event) { + var data = { + key: keyInput.val(), + value: valueInput.val() + }; + + /* + * Persist the new key-value pair. + * Clear the inputs. + * Set keyboard focus to key input: ready to start typing. + */ + $.post('/add', data, function() { + refreshTable(); + keyInput.val(''); + valueInput.val(''); + keyInput.focus(); + }); + /* Prevent HTTP form submit */ + event.preventDefault(); + }); + + /* Focus keyboard input into key input; ready to start typing */ + keyInput.focus(); +}); diff --git a/APP/src/main/resources/templates/index.html b/APP/src/main/resources/templates/index.html new file mode 100755 index 0000000..9fdef13 --- /dev/null +++ b/APP/src/main/resources/templates/index.html @@ -0,0 +1,43 @@ + + + +Intro to Redis with Spring Boot + + + + +
+
+
+
+
+ + +
+
+ + +
+ +
+
+
+ + + + + + + + + + +
KeyValue
+
+
+
+ + + + + diff --git a/APP/src/test/java/com/webapp/app/AppApplicationTests.java b/APP/src/test/java/com/webapp/app/AppApplicationTests.java new file mode 100644 index 0000000..60eac84 --- /dev/null +++ b/APP/src/test/java/com/webapp/app/AppApplicationTests.java @@ -0,0 +1,13 @@ +package com.webapp.app; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class AppApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/docs/README.md b/docs/README.md index f875005..d299a17 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,4 @@ - + ## Task 1: diff --git a/scripts/builder.sh b/scripts/builder.sh new file mode 100755 index 0000000..1bf3ad3 --- /dev/null +++ b/scripts/builder.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +GIT_REPO_URL="" +DOCKER_UNAME="saifnuaimii" +DOCKER_UPASS="S9494@@if" +DOCKER_IMAGE=$DOCKER_UNAME/app +DOCKER_IMAGE_VERSION="v1.0.0" + +#git clone $GIT_REPO_URL APP +#cd APP + +#---------------------------------------------------------- + +function docker_tag_exists() { + TOKEN=$(curl -s -H "Content-Type: application/json" -X POST -d '{"username": "'${DOCKER_UNAME}'", "password": "'${DOCKER_UPASS}'"}' https://hub.docker.com/v2/users/login/ | jq -r .token) + EXISTS=$(curl -s -H "Authorization: JWT ${TOKEN}" https://hub.docker.com/v2/repositories/$DOCKER_IMAGE/tags/\?page_size\=10000 | jq -r "[.results | .[] | .name == \"$DOCKER_IMAGE_VERSION\"] | any") + test $EXISTS = true +} + +if docker_tag_exists $DOCKER_IMAGE $DOCKER_IMAGE_VERSION; then + echo $DOCKER_IMAGE $DOCKER_IMAGE_VERSION exist +else + echo "missing/wrong version.Kindly check your docker repo or fix image name and tag" + exit 1 +fi +#---------------------------------------------------------- +cd .. +docker build -t ${DOCKER_IMAGE}:${DOCKER_IMAGE_VERSION} -f APP/Dockerfile . + +MY_DOCKER_RANDOM_NAME=myapp$RANDOM + +docker run -d --rm --name $MY_DOCKER_RANDOM_NAME ${DOCKER_IMAGE}:${DOCKER_IMAGE_VERSION} +MY_DOCKER_ID=$(docker ps -aqf "name=$MY_DOCKER_RANDOM_NAME") +CONTAINER_STATUS=$(docker inspect -f {{.State.Running}} $MY_DOCKER_ID) + +if [ $CONTAINER_STATUS = "true" ]; then + echo "Your application working fine" +else + echo "missing/wrong .Kindly check your docker continer" + exit 1 +fi +docker stop $MY_DOCKER_RANDOM_NAME +docker push $DOCKER_IMAGE \ No newline at end of file From 67e7542ceecf4f1d489d7d2661d9e1a4806cdd08 Mon Sep 17 00:00:00 2001 From: Saif Al Nuaimi Date: Sat, 29 Feb 2020 18:49:07 +0200 Subject: [PATCH 02/18] feat: commit --- APP-helm/Chart.yaml | 5 ++ APP-helm/app.properties | 3 + APP-helm/deploy.sh | 14 +++++ APP-helm/requirements.yaml | 4 ++ APP-helm/templates/configmap.yaml | 11 ++++ APP-helm/templates/deployment.yaml | 55 ++++++++++++++++++ APP-helm/templates/service.yaml | 19 ++++++ APP-helm/values.yaml | 43 ++++++++++++++ APP/docker-compose.yml | 21 ------- APP/local.dockerfile | 14 ----- docs/Tech Skills.odt | Bin 14676 -> 17593 bytes docs/steps.txt | 20 +++++++ {APP => task1/APP}/.gitignore | 0 .../.mvn/wrapper/MavenWrapperDownloader.java | 0 .../APP}/.mvn/wrapper/maven-wrapper.jar | Bin .../.mvn/wrapper/maven-wrapper.properties | 0 {APP => task1/APP}/mvnw | 0 {APP => task1/APP}/mvnw.cmd | 0 {APP => task1/APP}/pom.xml | 0 .../java/com/webapp/app/AppApplication.java | 0 .../com/webapp/app/config/RedisConfig.java | 0 .../webapp/app/controller/CounterServlet.java | 3 +- .../main/java/com/webapp/app/model/User.java | 0 .../app/repository/RedisRepository.java | 0 .../app/repository/RedisRepositoryImpl.java | 0 .../src/main/resources/application.properties | 2 +- .../APP}/src/main/resources/static/main.js | 0 .../src/main/resources/templates/index.html | 0 .../com/webapp/app/AppApplicationTests.java | 0 {APP => task2}/Dockerfile | 13 ++++- task2/scripts/builder.sh | 42 +++++++++++++ 31 files changed, 228 insertions(+), 41 deletions(-) create mode 100644 APP-helm/Chart.yaml create mode 100644 APP-helm/app.properties create mode 100644 APP-helm/deploy.sh create mode 100644 APP-helm/requirements.yaml create mode 100644 APP-helm/templates/configmap.yaml create mode 100644 APP-helm/templates/deployment.yaml create mode 100644 APP-helm/templates/service.yaml create mode 100644 APP-helm/values.yaml delete mode 100644 APP/docker-compose.yml delete mode 100644 APP/local.dockerfile rename {APP => task1/APP}/.gitignore (100%) rename {APP => task1/APP}/.mvn/wrapper/MavenWrapperDownloader.java (100%) rename {APP => task1/APP}/.mvn/wrapper/maven-wrapper.jar (100%) rename {APP => task1/APP}/.mvn/wrapper/maven-wrapper.properties (100%) rename {APP => task1/APP}/mvnw (100%) rename {APP => task1/APP}/mvnw.cmd (100%) rename {APP => task1/APP}/pom.xml (100%) rename {APP => task1/APP}/src/main/java/com/webapp/app/AppApplication.java (100%) rename {APP => task1/APP}/src/main/java/com/webapp/app/config/RedisConfig.java (100%) rename {APP => task1/APP}/src/main/java/com/webapp/app/controller/CounterServlet.java (91%) rename {APP => task1/APP}/src/main/java/com/webapp/app/model/User.java (100%) rename {APP => task1/APP}/src/main/java/com/webapp/app/repository/RedisRepository.java (100%) rename {APP => task1/APP}/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java (100%) rename {APP => task1/APP}/src/main/resources/application.properties (55%) rename {APP => task1/APP}/src/main/resources/static/main.js (100%) rename {APP => task1/APP}/src/main/resources/templates/index.html (100%) rename {APP => task1/APP}/src/test/java/com/webapp/app/AppApplicationTests.java (100%) rename {APP => task2}/Dockerfile (51%) create mode 100755 task2/scripts/builder.sh diff --git a/APP-helm/Chart.yaml b/APP-helm/Chart.yaml new file mode 100644 index 0000000..5ad49c5 --- /dev/null +++ b/APP-helm/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +appVersion: "1.0" +description: A Helm chart for Kubernetes +name: my-app +version: 0.1.0 diff --git a/APP-helm/app.properties b/APP-helm/app.properties new file mode 100644 index 0000000..8dac551 --- /dev/null +++ b/APP-helm/app.properties @@ -0,0 +1,3 @@ +spring.redis.host: "redis-server" +spring.redis.port: "6379" +server.port: "8080" \ No newline at end of file diff --git a/APP-helm/deploy.sh b/APP-helm/deploy.sh new file mode 100644 index 0000000..c936506 --- /dev/null +++ b/APP-helm/deploy.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +TILLER-NAMESPACE="" +NAMESPACE="" + +helm upgrade \ + --install \ + --wait \ + --force \ + --timeout 900 \ + --tiller-namespace=$TILLER-NAMESPACE \ + --namespace=$NAMESPACE \ + my-app \ + . diff --git a/APP-helm/requirements.yaml b/APP-helm/requirements.yaml new file mode 100644 index 0000000..947917f --- /dev/null +++ b/APP-helm/requirements.yaml @@ -0,0 +1,4 @@ +dependencies: + - name: redis + version: 9.3.2 + repository: https://kubernetes-charts.storage.googleapis.com/ \ No newline at end of file diff --git a/APP-helm/templates/configmap.yaml b/APP-helm/templates/configmap.yaml new file mode 100644 index 0000000..7436f93 --- /dev/null +++ b/APP-helm/templates/configmap.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ tpl (.Values.app.env.config.name ) . }} + namespace: .Values.app.env.config.namespace + labels: + chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +data: + {{ tpl ((.Files.Glob .Values.app.env.config.source ).AsConfig | indent 2 ) . }} \ No newline at end of file diff --git a/APP-helm/templates/deployment.yaml b/APP-helm/templates/deployment.yaml new file mode 100644 index 0000000..a94494c --- /dev/null +++ b/APP-helm/templates/deployment.yaml @@ -0,0 +1,55 @@ +apiVersion: apps/v1beta2 +kind: Deployment +metadata: + name: my-app + labels: + app: my-app + helm.sh/chart: my-app-0.1.0 + app.kubernetes.io/instance: my-app + app.kubernetes.io/managed-by: Tiller +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + app: my-app + app.kubernetes.io/instance: my-app + template: + metadata: + labels: + app: my-app + app.kubernetes.io/instance: my-app + spec: + containers: + - name: my-app + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + envFrom: + - configMapRef: + name: {{ tpl ( .Values.app.env.config.name ) .}} + ports: + - name: http + containerPort: {{ .Values.service.internalPort }} + protocol: TCP + livenessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 120 + periodSeconds: 120 + timeoutSeconds: 5 + failureThreshold: 5 + readinessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 120 + periodSeconds: 5 + timeoutSeconds: 5 + failureThreshold: 5 + resources: + limits: + cpu: 1000m + memory: 1500Mi + requests: + cpu: 50m + memory: 256Mi \ No newline at end of file diff --git a/APP-helm/templates/service.yaml b/APP-helm/templates/service.yaml new file mode 100644 index 0000000..3456c55 --- /dev/null +++ b/APP-helm/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: my-app + labels: + app: my-app + helm.sh/chart: my-app-0.1.0 + app.kubernetes.io/instance: my-app + app.kubernetes.io/managed-by: Tiller +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.externalPort }} + targetPort: {{ .Values.service.internalPort }} + protocol: TCP + name: http + selector: + app: my-app + app.kubernetes.io/instance: my-app diff --git a/APP-helm/values.yaml b/APP-helm/values.yaml new file mode 100644 index 0000000..6cf5042 --- /dev/null +++ b/APP-helm/values.yaml @@ -0,0 +1,43 @@ +replicaCount: 3 + +image: + repository: saifnuaimii/app + tag: v1.0.0 + pullPolicy: Always + +service: + name: my-app + type: ClusterIP + internalPort: 8080 + externalPort: 80 + +redis: + master: + persistence: + enabled: true + livenessProbe: + enabled: false + readinessProbe: + enabled: false + +ingress: + enabled: false + annotations: {} + path: / + hosts: + - chart-example.local + tls: [] + +app: + env: + config: + source: app.properties + name: configmap-{{ .Values.app.env.config.source }} + namespace: "" +resources: + limits: + cpu: 1000m + memory: 1500Mi + requests: + cpu: 50m + memory: 256Mi \ No newline at end of file diff --git a/APP/docker-compose.yml b/APP/docker-compose.yml deleted file mode 100644 index 83ab976..0000000 --- a/APP/docker-compose.yml +++ /dev/null @@ -1,21 +0,0 @@ -version: '2' -services: - webapp: - build: - context: . - dockerfile: local.dockerfile - ports: - - 8080:8080 - environment: - - SPRING_REDIS_HOST=redis-db - depends_on: - - redis-db - links: - - redis-db - - #---------------------------------- - redis-db: - restart: always - image: redis - ports: - - "6379:6379" \ No newline at end of file diff --git a/APP/local.dockerfile b/APP/local.dockerfile deleted file mode 100644 index 0c92d5e..0000000 --- a/APP/local.dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 -EXPOSE 8080 -RUN apk add redis -WORKDIR /myapp -COPY src /myapp/src -COPY pom.xml /myapp/pom.xml -RUN mvn clean install - -FROM openjdk:8-jre-alpine as mvn_runner_stage2 -WORKDIR /myrunningapp -ARG hostname=saifnuaimii/app:v1.0.0 -EXPOSE 8080 -COPY --from=mvn_builder_stage1 /myapp . -CMD java $JAVA_OPTS -D -jar target/app-*.jar \ No newline at end of file diff --git a/docs/Tech Skills.odt b/docs/Tech Skills.odt index 7d34c282062c0d0db067337c151528f44f2c0f9a..87e8fc393f859927690d13c198a014fa51df55f6 100644 GIT binary patch literal 17593 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 literal 14676 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 MyfVx&%iF*I11bt682|tP diff --git a/docs/steps.txt b/docs/steps.txt index 58ae2ce..156340c 100644 --- a/docs/steps.txt +++ b/docs/steps.txt @@ -4,10 +4,30 @@ ________________ Task 1: +First of all I created a pom file dependency file that has all of the dependency i need (web app, redis, health checker) +at my CounterServlet.java controller file a add a annotations to till set my application as spring boot application. + +I started with my hello world web app then how to get the count of the victors for my web app then get the ip address. + +Now I start to configuration the redis data base to get requites but i could not do that by my self honestly I asked i friend +to help my with rides implementation. :/ + +I save my app under /task1/APP and started with the extra: + - the health check ready from the pom file dependency implementation. + +note: you need to save ip and user-agent to the data base and implement right way. ________________ Task 2: +So I started by create a builder.sh file that first think first check if the repo i needed is there of not. + +Create a dockerfile and read more about multi-stage docker images build the first one build my app and the second one +run it. + +Implement ability to run the image in debug mode and set network=host and the rest of args. + + ________________ Task 3: diff --git a/APP/.gitignore b/task1/APP/.gitignore similarity index 100% rename from APP/.gitignore rename to task1/APP/.gitignore diff --git a/APP/.mvn/wrapper/MavenWrapperDownloader.java b/task1/APP/.mvn/wrapper/MavenWrapperDownloader.java similarity index 100% rename from APP/.mvn/wrapper/MavenWrapperDownloader.java rename to task1/APP/.mvn/wrapper/MavenWrapperDownloader.java diff --git a/APP/.mvn/wrapper/maven-wrapper.jar b/task1/APP/.mvn/wrapper/maven-wrapper.jar similarity index 100% rename from APP/.mvn/wrapper/maven-wrapper.jar rename to task1/APP/.mvn/wrapper/maven-wrapper.jar diff --git a/APP/.mvn/wrapper/maven-wrapper.properties b/task1/APP/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from APP/.mvn/wrapper/maven-wrapper.properties rename to task1/APP/.mvn/wrapper/maven-wrapper.properties diff --git a/APP/mvnw b/task1/APP/mvnw similarity index 100% rename from APP/mvnw rename to task1/APP/mvnw diff --git a/APP/mvnw.cmd b/task1/APP/mvnw.cmd similarity index 100% rename from APP/mvnw.cmd rename to task1/APP/mvnw.cmd diff --git a/APP/pom.xml b/task1/APP/pom.xml similarity index 100% rename from APP/pom.xml rename to task1/APP/pom.xml diff --git a/APP/src/main/java/com/webapp/app/AppApplication.java b/task1/APP/src/main/java/com/webapp/app/AppApplication.java similarity index 100% rename from APP/src/main/java/com/webapp/app/AppApplication.java rename to task1/APP/src/main/java/com/webapp/app/AppApplication.java diff --git a/APP/src/main/java/com/webapp/app/config/RedisConfig.java b/task1/APP/src/main/java/com/webapp/app/config/RedisConfig.java similarity index 100% rename from APP/src/main/java/com/webapp/app/config/RedisConfig.java rename to task1/APP/src/main/java/com/webapp/app/config/RedisConfig.java diff --git a/APP/src/main/java/com/webapp/app/controller/CounterServlet.java b/task1/APP/src/main/java/com/webapp/app/controller/CounterServlet.java similarity index 91% rename from APP/src/main/java/com/webapp/app/controller/CounterServlet.java rename to task1/APP/src/main/java/com/webapp/app/controller/CounterServlet.java index 0a67643..0b3f47c 100644 --- a/APP/src/main/java/com/webapp/app/controller/CounterServlet.java +++ b/task1/APP/src/main/java/com/webapp/app/controller/CounterServlet.java @@ -41,8 +41,7 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro PrintWriter out = response.getWriter(); out.println("Hello World !"); - out.println("

Welcome to my website !


"); - out.println("You are visitor number: " + (++iHitCounter)); + out.println("Welcome to my website !"); out.println("IP number: " + (remoteAddr)); out.println("count : " + (user.getCount())); } diff --git a/APP/src/main/java/com/webapp/app/model/User.java b/task1/APP/src/main/java/com/webapp/app/model/User.java similarity index 100% rename from APP/src/main/java/com/webapp/app/model/User.java rename to task1/APP/src/main/java/com/webapp/app/model/User.java diff --git a/APP/src/main/java/com/webapp/app/repository/RedisRepository.java b/task1/APP/src/main/java/com/webapp/app/repository/RedisRepository.java similarity index 100% rename from APP/src/main/java/com/webapp/app/repository/RedisRepository.java rename to task1/APP/src/main/java/com/webapp/app/repository/RedisRepository.java diff --git a/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java b/task1/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java similarity index 100% rename from APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java rename to task1/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java diff --git a/APP/src/main/resources/application.properties b/task1/APP/src/main/resources/application.properties similarity index 55% rename from APP/src/main/resources/application.properties rename to task1/APP/src/main/resources/application.properties index 9104b8c..7e42f9d 100644 --- a/APP/src/main/resources/application.properties +++ b/task1/APP/src/main/resources/application.properties @@ -1,3 +1,3 @@ -spring.redis.host=localhost +spring.redis.host=redis-server spring.redis.port=6379 server.port=8080 \ No newline at end of file diff --git a/APP/src/main/resources/static/main.js b/task1/APP/src/main/resources/static/main.js similarity index 100% rename from APP/src/main/resources/static/main.js rename to task1/APP/src/main/resources/static/main.js diff --git a/APP/src/main/resources/templates/index.html b/task1/APP/src/main/resources/templates/index.html similarity index 100% rename from APP/src/main/resources/templates/index.html rename to task1/APP/src/main/resources/templates/index.html diff --git a/APP/src/test/java/com/webapp/app/AppApplicationTests.java b/task1/APP/src/test/java/com/webapp/app/AppApplicationTests.java similarity index 100% rename from APP/src/test/java/com/webapp/app/AppApplicationTests.java rename to task1/APP/src/test/java/com/webapp/app/AppApplicationTests.java diff --git a/APP/Dockerfile b/task2/Dockerfile similarity index 51% rename from APP/Dockerfile rename to task2/Dockerfile index 9f22648..9743ae2 100644 --- a/APP/Dockerfile +++ b/task2/Dockerfile @@ -1,13 +1,20 @@ FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 EXPOSE 8080 WORKDIR /myapp -COPY APP/src /myapp/src -COPY APP/pom.xml /myapp/pom.xml +COPY scripts/devops-task/APP/src /myapp/src +COPY scripts/devops-task/APP/src /myapp/pom.xml RUN mvn clean install FROM openjdk:8-jre-alpine as mvn_runner_stage2 + +ARG JAVA_OPTS="-agentlib:jdwp=transport=dt_socket,address=5005,server=y,suspend=n" +ARG network="host" +ARG IMAGE="" +ARG REPO="" +ARG PATH="" +ARG TAG="v1.0" + WORKDIR /myrunningapp -ARG hostname=saifnuaimii/app:v1.0.0 EXPOSE 8080 COPY --from=mvn_builder_stage1 /myapp . CMD java $JAVA_OPTS -D -jar target/app-*.jar \ No newline at end of file diff --git a/task2/scripts/builder.sh b/task2/scripts/builder.sh new file mode 100755 index 0000000..38fee32 --- /dev/null +++ b/task2/scripts/builder.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set e +GIT_REPO_URL="https://github.com/saifal-nuaimi/devops-task.git" +DOCKER_UNAME="saifnuaimii" +DOCKER_UPASS="S9494@@if" +DOCKER_IMAGE=$DOCKER_UNAME/app +DOCKER_IMAGE_VERSION="v1.0.0" + +git clone $GIT_REPO_URL + +#---------------------------------------------------------- + +function docker_tag_exists() { + TOKEN=$(curl -s -H "Content-Type: application/json" -X POST -d '{"username": "'${DOCKER_UNAME}'", "password": "'${DOCKER_UPASS}'"}' https://hub.docker.com/v2/users/login/ | jq -r .token) + EXISTS=$(curl -s -H "Authorization: JWT ${TOKEN}" https://hub.docker.com/v2/repositories/$DOCKER_IMAGE/tags/\?page_size\=10000 | jq -r "[.results | .[] | .name == \"$DOCKER_IMAGE_VERSION\"] | any") + test $EXISTS = true +} + +if docker_tag_exists $DOCKER_IMAGE $DOCKER_IMAGE_VERSION; then + echo $DOCKER_IMAGE $DOCKER_IMAGE_VERSION exist +else + echo "missing/wrong version.Kindly check your docker repo or fix image name and tag" + exit 1 +fi +#---------------------------------------------------------- +cd .. +docker build -t ${DOCKER_IMAGE}:${DOCKER_IMAGE_VERSION} -f Dockerfile . + +MY_DOCKER_RANDOM_NAME=myapp$RANDOM + +docker run -d --rm --name $MY_DOCKER_RANDOM_NAME ${DOCKER_IMAGE}:${DOCKER_IMAGE_VERSION} +MY_DOCKER_ID=$(docker ps -aqf "name=$MY_DOCKER_RANDOM_NAME") +CONTAINER_STATUS=$(docker inspect -f {{.State.Running}} $MY_DOCKER_ID) + +if [ $CONTAINER_STATUS = "true" ]; then + echo "Your application working fine" +else + echo "missing/wrong .Kindly check your docker continer" + exit 1 +fi +docker stop $MY_DOCKER_RANDOM_NAME +docker push $DOCKER_IMAGE \ No newline at end of file From 8c3c9cc2ca6ce1393b50d435286e2159762f0716 Mon Sep 17 00:00:00 2001 From: Saif Al Nuaimi Date: Sat, 29 Feb 2020 18:58:40 +0200 Subject: [PATCH 03/18] feat: commit --- APP/.gitignore | 31 ++ APP/.mvn/wrapper/MavenWrapperDownloader.java | 118 +++++++ APP/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes APP/.mvn/wrapper/maven-wrapper.properties | 2 + APP/Dockerfile | 13 + APP/docker-compose.yml | 15 + APP/local.dockerfile | 17 + APP/mvnw | 310 ++++++++++++++++++ APP/mvnw.cmd | 182 ++++++++++ APP/pom.xml | 66 ++++ .../java/com/webapp/app/AppApplication.java | 13 + .../com/webapp/app/config/RedisConfig.java | 30 ++ .../webapp/app/controller/CounterServlet.java | 56 ++++ .../main/java/com/webapp/app/model/User.java | 41 +++ .../app/repository/RedisRepository.java | 18 + .../app/repository/RedisRepositoryImpl.java | 49 +++ APP/src/main/resources/application.properties | 3 + APP/src/main/resources/static/main.js | 138 ++++++++ APP/src/main/resources/templates/index.html | 43 +++ .../com/webapp/app/AppApplicationTests.java | 13 + 20 files changed, 1158 insertions(+) create mode 100644 APP/.gitignore create mode 100644 APP/.mvn/wrapper/MavenWrapperDownloader.java create mode 100644 APP/.mvn/wrapper/maven-wrapper.jar create mode 100644 APP/.mvn/wrapper/maven-wrapper.properties create mode 100644 APP/Dockerfile create mode 100644 APP/docker-compose.yml create mode 100644 APP/local.dockerfile create mode 100644 APP/mvnw create mode 100644 APP/mvnw.cmd create mode 100644 APP/pom.xml create mode 100644 APP/src/main/java/com/webapp/app/AppApplication.java create mode 100644 APP/src/main/java/com/webapp/app/config/RedisConfig.java create mode 100644 APP/src/main/java/com/webapp/app/controller/CounterServlet.java create mode 100644 APP/src/main/java/com/webapp/app/model/User.java create mode 100644 APP/src/main/java/com/webapp/app/repository/RedisRepository.java create mode 100644 APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java create mode 100644 APP/src/main/resources/application.properties create mode 100644 APP/src/main/resources/static/main.js create mode 100644 APP/src/main/resources/templates/index.html create mode 100644 APP/src/test/java/com/webapp/app/AppApplicationTests.java diff --git a/APP/.gitignore b/APP/.gitignore new file mode 100644 index 0000000..a2a3040 --- /dev/null +++ b/APP/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/APP/.mvn/wrapper/MavenWrapperDownloader.java b/APP/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..a45eb6b --- /dev/null +++ b/APP/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/APP/.mvn/wrapper/maven-wrapper.jar b/APP/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 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 EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/APP/.mvn/wrapper/maven-wrapper.properties b/APP/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..642d572 --- /dev/null +++ b/APP/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/APP/Dockerfile b/APP/Dockerfile new file mode 100644 index 0000000..8f94c53 --- /dev/null +++ b/APP/Dockerfile @@ -0,0 +1,13 @@ +FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 +EXPOSE 8080 +WORKDIR /myapp +COPY APP/src /myapp/src +COPY APP/pom.xml /myapp/pom.xml +RUN mvn clean install + +FROM openjdk:8-jre-alpine as mvn_runner_stage2 +WORKDIR /myrunningapp +ARG hostname=saifnuaimii/app:v1.0.0 +EXPOSE 8080 +COPY --from=mvn_builder_stage1 /myapp . +CMD java -D -jar target/app-*.jar \ No newline at end of file diff --git a/APP/docker-compose.yml b/APP/docker-compose.yml new file mode 100644 index 0000000..c100b97 --- /dev/null +++ b/APP/docker-compose.yml @@ -0,0 +1,15 @@ +version: '2' +services: + redis: + image: redis:3.0.3 + hostname: redis-server + ports: + - "6379:6379" + app1: + build: + context: . + dockerfile: local.dockerfile + links: + - "redis:redis-server" + ports: + - "8080:8080" \ No newline at end of file diff --git a/APP/local.dockerfile b/APP/local.dockerfile new file mode 100644 index 0000000..5b5c653 --- /dev/null +++ b/APP/local.dockerfile @@ -0,0 +1,17 @@ +FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 +EXPOSE 8080 +RUN apk add redis +WORKDIR /myapp +COPY src /myapp/src +COPY pom.xml /myapp/pom.xml +RUN mvn clean install + +FROM openjdk:8-jre-alpine as mvn_runner_stage2 +WORKDIR /myrunningapp +ARG hostname=saifnuaimii/app:v1.0.0 +EXPOSE 8080 +COPY --from=mvn_builder_stage1 /myapp . +RUN apk add redis +CMD redis-cli -h redis-server -p 8080 + +CMD java -D -jar target/app-*.jar \ No newline at end of file diff --git a/APP/mvnw b/APP/mvnw new file mode 100644 index 0000000..a16b543 --- /dev/null +++ b/APP/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/APP/mvnw.cmd b/APP/mvnw.cmd new file mode 100644 index 0000000..c8d4337 --- /dev/null +++ b/APP/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/APP/pom.xml b/APP/pom.xml new file mode 100644 index 0000000..9fafa3f --- /dev/null +++ b/APP/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.4.RELEASE + + + com.webapp + app + 1.0.0-RELEASE + APP + Demo project for Spring Boot + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-data-redis + + + io.lettuce + lettuce-core + + + + + redis.clients + jedis + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/APP/src/main/java/com/webapp/app/AppApplication.java b/APP/src/main/java/com/webapp/app/AppApplication.java new file mode 100644 index 0000000..1b16909 --- /dev/null +++ b/APP/src/main/java/com/webapp/app/AppApplication.java @@ -0,0 +1,13 @@ +package com.webapp.app; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class AppApplication { + + public static void main(String[] args) { + SpringApplication.run(AppApplication.class, args); + } + +} diff --git a/APP/src/main/java/com/webapp/app/config/RedisConfig.java b/APP/src/main/java/com/webapp/app/config/RedisConfig.java new file mode 100644 index 0000000..12621a5 --- /dev/null +++ b/APP/src/main/java/com/webapp/app/config/RedisConfig.java @@ -0,0 +1,30 @@ +package com.webapp.app.config; + + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.listener.ChannelTopic; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.data.redis.serializer.GenericToStringSerializer; + +@Configuration +public class RedisConfig { + + @Bean + JedisConnectionFactory jedisConnectionFactory() { + JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(); + jedisConnectionFactory.getPoolConfig().setMaxIdle(30); + jedisConnectionFactory.getPoolConfig().setMinIdle(10); + return jedisConnectionFactory; + } + + @Bean + public RedisTemplate redisTemplate() { + final RedisTemplate template = new RedisTemplate(); + template.setConnectionFactory(jedisConnectionFactory()); + template.setValueSerializer(new GenericToStringSerializer(Object.class)); + return template; + } +} \ No newline at end of file diff --git a/APP/src/main/java/com/webapp/app/controller/CounterServlet.java b/APP/src/main/java/com/webapp/app/controller/CounterServlet.java new file mode 100644 index 0000000..0a67643 --- /dev/null +++ b/APP/src/main/java/com/webapp/app/controller/CounterServlet.java @@ -0,0 +1,56 @@ +package com.webapp.app.controller; + +import com.webapp.app.model.User; +import com.webapp.app.repository.RedisRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Objects; + + +@RestController +public class CounterServlet extends HttpServlet { + @Autowired + private RedisRepository redisRepository; + private int iHitCounter; + + public void init() throws ServletException { + iHitCounter = 0; + } + + @GetMapping("/") + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + String remoteAddr = ""; + + if (request != null) { + remoteAddr = request.getHeader("X-FORWARDED-FOR"); + if (remoteAddr == null || "".equals(remoteAddr)) { + remoteAddr = request.getRemoteAddr(); + } + User current = redisRepository.findUser(remoteAddr); + User user = new User(remoteAddr, Objects.nonNull(current) ? current.getCount() + 1 : 1); + redisRepository.add(user); + + PrintWriter out = response.getWriter(); + out.println("Hello World !"); + out.println("

Welcome to my website !


"); + out.println("You are visitor number: " + (++iHitCounter)); + out.println("IP number: " + (remoteAddr)); + out.println("count : " + (user.getCount())); + } + + } + + + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + doGet(request, response); + } +} \ No newline at end of file diff --git a/APP/src/main/java/com/webapp/app/model/User.java b/APP/src/main/java/com/webapp/app/model/User.java new file mode 100644 index 0000000..5829c9b --- /dev/null +++ b/APP/src/main/java/com/webapp/app/model/User.java @@ -0,0 +1,41 @@ +package com.webapp.app.model; + +import java.io.Serializable; + +public class User implements Serializable { + + + private static final long serialVersionUID = 1L; + private String id; + private int count; + + public User(String id, int count) { + this.id = id; + this.count = count; + } + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + @Override + public String toString() { + return "User{" + + "id='" + id + '\'' + + ", count=" + count + + '}'; + } +} diff --git a/APP/src/main/java/com/webapp/app/repository/RedisRepository.java b/APP/src/main/java/com/webapp/app/repository/RedisRepository.java new file mode 100644 index 0000000..3186cd3 --- /dev/null +++ b/APP/src/main/java/com/webapp/app/repository/RedisRepository.java @@ -0,0 +1,18 @@ +package com.webapp.app.repository; + + +import com.webapp.app.model.User; + +import java.util.Map; + +public interface RedisRepository { + + Map findAllUsers(); + + void add(User user); + + void delete(String id); + + User findUser(String id); + +} diff --git a/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java b/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java new file mode 100644 index 0000000..02ea8a1 --- /dev/null +++ b/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java @@ -0,0 +1,49 @@ +package com.webapp.app.repository; + +import com.webapp.app.model.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Repository; + +import javax.annotation.PostConstruct; +import java.util.Map; + +@Repository +public class RedisRepositoryImpl implements RedisRepository { + private static final String KEY = "User"; + + private RedisTemplate redisTemplate; + private HashOperations hashOperations; + + @Autowired + public RedisRepositoryImpl(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + @PostConstruct + private void init() { + hashOperations = redisTemplate.opsForHash(); + } + + public void add(final User user) { + hashOperations.put(KEY, user.getId(), user); + } + + public void delete(final String id) { + hashOperations.delete(KEY, id); + } + + public User findUser(final String id) { + Object obj = hashOperations.get(KEY, id); + if (obj instanceof User) + return (User) obj; + return new User("", 0); + } + + public Map findAllUsers() { + return hashOperations.entries(KEY); + } + + +} diff --git a/APP/src/main/resources/application.properties b/APP/src/main/resources/application.properties new file mode 100644 index 0000000..7e42f9d --- /dev/null +++ b/APP/src/main/resources/application.properties @@ -0,0 +1,3 @@ +spring.redis.host=redis-server +spring.redis.port=6379 +server.port=8080 \ No newline at end of file diff --git a/APP/src/main/resources/static/main.js b/APP/src/main/resources/static/main.js new file mode 100644 index 0000000..08c6332 --- /dev/null +++ b/APP/src/main/resources/static/main.js @@ -0,0 +1,138 @@ +/** + * Create HTML table row. + * + * \param text (str) HTML code to be placed inside the row. + */ +function tr(text) { + return '' + text + ''; +} + +/** + * Create HTML table cell element. + * + * \param text (str) The text to be placed inside the cell. + */ +function td(text) { + return '' + text + ''; +} + +/** + * Edit value: load key and value into inputs. + */ +function editButton(key) { + var format = ''; + return format.replace(/{key}/g, key); +} + +/** + * Create HTML button with which a key-value pair is deleted from persistence. + * + * \param key (str) that'll be deleted when the created button is clicked. + */ +function deleteButton(key) { + var format = '' + return format.replace(/{key}/g, key); +} + +/** + * Create HTML table row element. + * + * \param key (str) text into the key cell. + * \param value (str) text into the value cell. + */ +function row(key, value) { + return $( + tr( + td(key) + + td(value) + + td(editButton(key)) + + td(deleteButton(key)))); +} + +/** + * Clear and reload the values in data table. + */ +function refreshTable() { + $.get('/values', function(data) { + var attr, + mainTable = $('#mainTable tbody'); + mainTable.empty(); + for (attr in data) { + if (data.hasOwnProperty(attr)) { + mainTable.append(row(attr, data[attr])); + } + } + }); +} + +function editKey(key) { + /* Find the row with key in first column (key column). */ + var format = '#mainTable tbody td:first-child:contains("{key}")', + selector = format.replace(/{key}/, key), + cells = $(selector).parent().children(), + key = cells[0].textContent, + value = cells[1].textContent, + keyInput = $('#keyInput'), + valueInput = $('#valueInput'); + + /* Load the key and value texts into inputs + * Select value text so it can be directly typed to + */ + keyInput.val(key); + valueInput.val(value); + valueInput.select(); +} + +/** + * Delete key-value pair. + * + * \param key (str) The key to be deleted. + */ +function deleteKey(key) { + /* + * Delete the key. + * Reload keys and values in table to reflect the deleted ones. + * Set keyboard focus to key input: ready to start typing. + */ + $.post('/delete', {key: key}, function() { + refreshTable(); + $('#keyInput').focus(); + }); +} + +$(document).ready(function() { + var keyInput = $('#keyInput'), + valueInput = $('#valueInput'); + + refreshTable(); + $('#addForm').on('submit', function(event) { + var data = { + key: keyInput.val(), + value: valueInput.val() + }; + + /* + * Persist the new key-value pair. + * Clear the inputs. + * Set keyboard focus to key input: ready to start typing. + */ + $.post('/add', data, function() { + refreshTable(); + keyInput.val(''); + valueInput.val(''); + keyInput.focus(); + }); + /* Prevent HTTP form submit */ + event.preventDefault(); + }); + + /* Focus keyboard input into key input; ready to start typing */ + keyInput.focus(); +}); diff --git a/APP/src/main/resources/templates/index.html b/APP/src/main/resources/templates/index.html new file mode 100644 index 0000000..9fdef13 --- /dev/null +++ b/APP/src/main/resources/templates/index.html @@ -0,0 +1,43 @@ + + + +Intro to Redis with Spring Boot + + + + +
+
+
+
+
+ + +
+
+ + +
+ +
+
+
+ + + + + + + + + + +
KeyValue
+
+
+
+ + + + + diff --git a/APP/src/test/java/com/webapp/app/AppApplicationTests.java b/APP/src/test/java/com/webapp/app/AppApplicationTests.java new file mode 100644 index 0000000..60eac84 --- /dev/null +++ b/APP/src/test/java/com/webapp/app/AppApplicationTests.java @@ -0,0 +1,13 @@ +package com.webapp.app; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class AppApplicationTests { + + @Test + void contextLoads() { + } + +} From e55953666abcd5c00e9640a785593fbbbca5917c Mon Sep 17 00:00:00 2001 From: Saif Al Nuaimi Date: Sat, 29 Feb 2020 19:21:01 +0200 Subject: [PATCH 04/18] feat: commit --- APP/Dockerfile | 4 ++-- task2/Dockerfile | 20 -------------------- task2/scripts/builder.sh | 3 ++- 3 files changed, 4 insertions(+), 23 deletions(-) delete mode 100644 task2/Dockerfile diff --git a/APP/Dockerfile b/APP/Dockerfile index 8f94c53..22a3e37 100644 --- a/APP/Dockerfile +++ b/APP/Dockerfile @@ -1,8 +1,8 @@ FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 EXPOSE 8080 WORKDIR /myapp -COPY APP/src /myapp/src -COPY APP/pom.xml /myapp/pom.xml +COPY src /myapp/src +COPY pom.xml /myapp/pom.xml RUN mvn clean install FROM openjdk:8-jre-alpine as mvn_runner_stage2 diff --git a/task2/Dockerfile b/task2/Dockerfile deleted file mode 100644 index 9743ae2..0000000 --- a/task2/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 -EXPOSE 8080 -WORKDIR /myapp -COPY scripts/devops-task/APP/src /myapp/src -COPY scripts/devops-task/APP/src /myapp/pom.xml -RUN mvn clean install - -FROM openjdk:8-jre-alpine as mvn_runner_stage2 - -ARG JAVA_OPTS="-agentlib:jdwp=transport=dt_socket,address=5005,server=y,suspend=n" -ARG network="host" -ARG IMAGE="" -ARG REPO="" -ARG PATH="" -ARG TAG="v1.0" - -WORKDIR /myrunningapp -EXPOSE 8080 -COPY --from=mvn_builder_stage1 /myapp . -CMD java $JAVA_OPTS -D -jar target/app-*.jar \ No newline at end of file diff --git a/task2/scripts/builder.sh b/task2/scripts/builder.sh index 38fee32..d8abd52 100755 --- a/task2/scripts/builder.sh +++ b/task2/scripts/builder.sh @@ -23,7 +23,8 @@ else exit 1 fi #---------------------------------------------------------- -cd .. +cd devops-task/APP +pwd docker build -t ${DOCKER_IMAGE}:${DOCKER_IMAGE_VERSION} -f Dockerfile . MY_DOCKER_RANDOM_NAME=myapp$RANDOM From 55bd698dcafabe561cb77e47671e4f1a84463aa1 Mon Sep 17 00:00:00 2001 From: Saif Al Nuaimi Date: Sat, 29 Feb 2020 20:06:43 +0200 Subject: [PATCH 05/18] feat: commit --- docs/steps.txt | 13 +- scripts/builder.sh | 42 -- task2/scripts/builder.sh | 46 +- task3/APP/.gitignore | 31 + .../.mvn/wrapper/MavenWrapperDownloader.java | 118 ++++ task3/APP/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes .../APP/.mvn/wrapper/maven-wrapper.properties | 2 + task3/APP/docker-compose.yml | 13 + task3/APP/mvnw | 310 ++++++++++ task3/APP/mvnw.cmd | 182 ++++++ task3/APP/pom.xml | 66 +++ .../java/com/webapp/app/AppApplication.java | 13 + .../com/webapp/app/config/RedisConfig.java | 30 + .../webapp/app/controller/CounterServlet.java | 56 ++ .../main/java/com/webapp/app/model/User.java | 41 ++ .../app/repository/RedisRepository.java | 18 + .../app/repository/RedisRepositoryImpl.java | 49 ++ .../src/main/resources/application.properties | 3 + task3/APP/src/main/resources/static/main.js | 138 +++++ .../src/main/resources/templates/index.html | 43 ++ .../com/webapp/app/AppApplicationTests.java | 13 + {APP-helm => task5/APP-helm}/Chart.yaml | 0 {APP-helm => task5/APP-helm}/app.properties | 0 task5/APP-helm/charts/redis/.helmignore | 3 + task5/APP-helm/charts/redis/Chart.yaml | 21 + task5/APP-helm/charts/redis/README.md | 463 +++++++++++++++ .../charts/redis/ci/default-values.yaml | 1 + .../APP-helm/charts/redis/ci/dev-values.yaml | 9 + .../charts/redis/ci/extra-flags-values.yaml | 11 + .../redis/ci/production-sentinel-values.yaml | 521 ++++++++++++++++ .../charts/redis/ci/production-values.yaml | 522 ++++++++++++++++ .../charts/redis/ci/redis-lib-values.yaml | 13 + .../redis/ci/redisgraph-module-values.yaml | 10 + .../APP-helm/charts/redis/templates/NOTES.txt | 104 ++++ .../charts/redis/templates/_helpers.tpl | 320 ++++++++++ .../charts/redis/templates/configmap.yaml | 52 ++ .../charts/redis/templates/headless-svc.yaml | 24 + .../redis/templates/health-configmap.yaml | 134 +++++ .../redis/templates/metrics-prometheus.yaml | 30 + .../charts/redis/templates/metrics-svc.yaml | 30 + .../charts/redis/templates/networkpolicy.yaml | 65 ++ .../templates/redis-master-statefulset.yaml | 408 +++++++++++++ .../redis/templates/redis-master-svc.yaml | 34 ++ .../charts/redis/templates/redis-role.yaml | 13 + .../redis/templates/redis-rolebinding.yaml | 18 + .../redis/templates/redis-serviceaccount.yaml | 11 + .../templates/redis-slave-statefulset.yaml | 426 +++++++++++++ .../redis/templates/redis-slave-svc.yaml | 35 ++ .../templates/redis-with-sentinel-svc.yaml | 40 ++ .../charts/redis/templates/secret.yaml | 18 + .../charts/redis/values-production.yaml | 559 ++++++++++++++++++ task5/APP-helm/charts/redis/values.yaml | 558 +++++++++++++++++ {APP-helm => task5/APP-helm}/deploy.sh | 0 .../APP-helm}/requirements.yaml | 0 .../APP-helm}/templates/configmap.yaml | 0 .../APP-helm}/templates/deployment.yaml | 0 .../APP-helm}/templates/service.yaml | 0 {APP-helm => task5/APP-helm}/values.yaml | 0 58 files changed, 5626 insertions(+), 54 deletions(-) delete mode 100755 scripts/builder.sh create mode 100644 task3/APP/.gitignore create mode 100644 task3/APP/.mvn/wrapper/MavenWrapperDownloader.java create mode 100644 task3/APP/.mvn/wrapper/maven-wrapper.jar create mode 100644 task3/APP/.mvn/wrapper/maven-wrapper.properties create mode 100644 task3/APP/docker-compose.yml create mode 100644 task3/APP/mvnw create mode 100644 task3/APP/mvnw.cmd create mode 100644 task3/APP/pom.xml create mode 100644 task3/APP/src/main/java/com/webapp/app/AppApplication.java create mode 100644 task3/APP/src/main/java/com/webapp/app/config/RedisConfig.java create mode 100644 task3/APP/src/main/java/com/webapp/app/controller/CounterServlet.java create mode 100644 task3/APP/src/main/java/com/webapp/app/model/User.java create mode 100644 task3/APP/src/main/java/com/webapp/app/repository/RedisRepository.java create mode 100644 task3/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java create mode 100644 task3/APP/src/main/resources/application.properties create mode 100644 task3/APP/src/main/resources/static/main.js create mode 100644 task3/APP/src/main/resources/templates/index.html create mode 100644 task3/APP/src/test/java/com/webapp/app/AppApplicationTests.java rename {APP-helm => task5/APP-helm}/Chart.yaml (100%) rename {APP-helm => task5/APP-helm}/app.properties (100%) create mode 100755 task5/APP-helm/charts/redis/.helmignore create mode 100755 task5/APP-helm/charts/redis/Chart.yaml create mode 100755 task5/APP-helm/charts/redis/README.md create mode 100755 task5/APP-helm/charts/redis/ci/default-values.yaml create mode 100755 task5/APP-helm/charts/redis/ci/dev-values.yaml create mode 100755 task5/APP-helm/charts/redis/ci/extra-flags-values.yaml create mode 100755 task5/APP-helm/charts/redis/ci/production-sentinel-values.yaml create mode 100755 task5/APP-helm/charts/redis/ci/production-values.yaml create mode 100755 task5/APP-helm/charts/redis/ci/redis-lib-values.yaml create mode 100755 task5/APP-helm/charts/redis/ci/redisgraph-module-values.yaml create mode 100755 task5/APP-helm/charts/redis/templates/NOTES.txt create mode 100755 task5/APP-helm/charts/redis/templates/_helpers.tpl create mode 100755 task5/APP-helm/charts/redis/templates/configmap.yaml create mode 100755 task5/APP-helm/charts/redis/templates/headless-svc.yaml create mode 100755 task5/APP-helm/charts/redis/templates/health-configmap.yaml create mode 100755 task5/APP-helm/charts/redis/templates/metrics-prometheus.yaml create mode 100755 task5/APP-helm/charts/redis/templates/metrics-svc.yaml create mode 100755 task5/APP-helm/charts/redis/templates/networkpolicy.yaml create mode 100755 task5/APP-helm/charts/redis/templates/redis-master-statefulset.yaml create mode 100755 task5/APP-helm/charts/redis/templates/redis-master-svc.yaml create mode 100755 task5/APP-helm/charts/redis/templates/redis-role.yaml create mode 100755 task5/APP-helm/charts/redis/templates/redis-rolebinding.yaml create mode 100755 task5/APP-helm/charts/redis/templates/redis-serviceaccount.yaml create mode 100755 task5/APP-helm/charts/redis/templates/redis-slave-statefulset.yaml create mode 100755 task5/APP-helm/charts/redis/templates/redis-slave-svc.yaml create mode 100755 task5/APP-helm/charts/redis/templates/redis-with-sentinel-svc.yaml create mode 100755 task5/APP-helm/charts/redis/templates/secret.yaml create mode 100755 task5/APP-helm/charts/redis/values-production.yaml create mode 100755 task5/APP-helm/charts/redis/values.yaml rename {APP-helm => task5/APP-helm}/deploy.sh (100%) rename {APP-helm => task5/APP-helm}/requirements.yaml (100%) rename {APP-helm => task5/APP-helm}/templates/configmap.yaml (100%) rename {APP-helm => task5/APP-helm}/templates/deployment.yaml (100%) rename {APP-helm => task5/APP-helm}/templates/service.yaml (100%) rename {APP-helm => task5/APP-helm}/values.yaml (100%) diff --git a/docs/steps.txt b/docs/steps.txt index 156340c..2257a97 100644 --- a/docs/steps.txt +++ b/docs/steps.txt @@ -9,9 +9,11 @@ at my CounterServlet.java controller file a add a annotations to till set my app I started with my hello world web app then how to get the count of the victors for my web app then get the ip address. -Now I start to configuration the redis data base to get requites but i could not do that by my self honestly I asked i friend +Now I start to configuration the redis database to get requites but i could not do that by my self honestly I asked i friend to help my with rides implementation. :/ +I could not implement rides database the right way but you can test it by run it locally. + I save my app under /task1/APP and started with the extra: - the health check ready from the pom file dependency implementation. @@ -27,17 +29,26 @@ run it. Implement ability to run the image in debug mode and set network=host and the rest of args. +Test that my application image and container is working fine push it and save it. +extra points: + - Build the exit status codes. ________________ Task 3: +Build docker-compose from two parts first part is the database part and the second one is the app part. + +Add all the configuration that the app need on the resources file. ________________ Task 4: + ________________ Task 5: +Start build my helm chart using basic files values.yaml requirements.yaml Chart.yaml + diff --git a/scripts/builder.sh b/scripts/builder.sh deleted file mode 100755 index 1bf3ad3..0000000 --- a/scripts/builder.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash -GIT_REPO_URL="" -DOCKER_UNAME="saifnuaimii" -DOCKER_UPASS="S9494@@if" -DOCKER_IMAGE=$DOCKER_UNAME/app -DOCKER_IMAGE_VERSION="v1.0.0" - -#git clone $GIT_REPO_URL APP -#cd APP - -#---------------------------------------------------------- - -function docker_tag_exists() { - TOKEN=$(curl -s -H "Content-Type: application/json" -X POST -d '{"username": "'${DOCKER_UNAME}'", "password": "'${DOCKER_UPASS}'"}' https://hub.docker.com/v2/users/login/ | jq -r .token) - EXISTS=$(curl -s -H "Authorization: JWT ${TOKEN}" https://hub.docker.com/v2/repositories/$DOCKER_IMAGE/tags/\?page_size\=10000 | jq -r "[.results | .[] | .name == \"$DOCKER_IMAGE_VERSION\"] | any") - test $EXISTS = true -} - -if docker_tag_exists $DOCKER_IMAGE $DOCKER_IMAGE_VERSION; then - echo $DOCKER_IMAGE $DOCKER_IMAGE_VERSION exist -else - echo "missing/wrong version.Kindly check your docker repo or fix image name and tag" - exit 1 -fi -#---------------------------------------------------------- -cd .. -docker build -t ${DOCKER_IMAGE}:${DOCKER_IMAGE_VERSION} -f APP/Dockerfile . - -MY_DOCKER_RANDOM_NAME=myapp$RANDOM - -docker run -d --rm --name $MY_DOCKER_RANDOM_NAME ${DOCKER_IMAGE}:${DOCKER_IMAGE_VERSION} -MY_DOCKER_ID=$(docker ps -aqf "name=$MY_DOCKER_RANDOM_NAME") -CONTAINER_STATUS=$(docker inspect -f {{.State.Running}} $MY_DOCKER_ID) - -if [ $CONTAINER_STATUS = "true" ]; then - echo "Your application working fine" -else - echo "missing/wrong .Kindly check your docker continer" - exit 1 -fi -docker stop $MY_DOCKER_RANDOM_NAME -docker push $DOCKER_IMAGE \ No newline at end of file diff --git a/task2/scripts/builder.sh b/task2/scripts/builder.sh index d8abd52..96fd5ed 100755 --- a/task2/scripts/builder.sh +++ b/task2/scripts/builder.sh @@ -11,21 +11,32 @@ git clone $GIT_REPO_URL #---------------------------------------------------------- function docker_tag_exists() { - TOKEN=$(curl -s -H "Content-Type: application/json" -X POST -d '{"username": "'${DOCKER_UNAME}'", "password": "'${DOCKER_UPASS}'"}' https://hub.docker.com/v2/users/login/ | jq -r .token) - EXISTS=$(curl -s -H "Authorization: JWT ${TOKEN}" https://hub.docker.com/v2/repositories/$DOCKER_IMAGE/tags/\?page_size\=10000 | jq -r "[.results | .[] | .name == \"$DOCKER_IMAGE_VERSION\"] | any") - test $EXISTS = true + TOKEN=$(curl -s -H "Content-Type: application/json" -X POST -d '{"username": "'${DOCKER_UNAME}'", "password": "'${DOCKER_UPASS}'"}' https://hub.docker.com/v2/users/login/ | jq -r .token) + EXISTS=$(curl -s -H "Authorization: JWT ${TOKEN}" https://hub.docker.com/v2/repositories/$DOCKER_IMAGE/tags/\?page_size\=10000 | jq -r "[.results | .[] | .name == \"$DOCKER_IMAGE_VERSION\"] | any") + test $EXISTS = true } if docker_tag_exists $DOCKER_IMAGE $DOCKER_IMAGE_VERSION; then - echo $DOCKER_IMAGE $DOCKER_IMAGE_VERSION exist + echo $DOCKER_IMAGE $DOCKER_IMAGE_VERSION exist + echo "3" else - echo "missing/wrong version.Kindly check your docker repo or fix image name and tag" - exit 1 + echo "2" + echo "missing/wrong version.Kindly check your docker repo or fix image name and tag" + exit 1 fi #---------------------------------------------------------- cd devops-task/APP pwd -docker build -t ${DOCKER_IMAGE}:${DOCKER_IMAGE_VERSION} -f Dockerfile . +function build_docker_image() { + docker build -t ${DOCKER_IMAGE}:${DOCKER_IMAGE_VERSION} -f Dockerfile . +} +if build_docker_image; then + echo "Building your image" + build_docker_image +else + echo "4" + exit 1 +fi MY_DOCKER_RANDOM_NAME=myapp$RANDOM @@ -33,11 +44,24 @@ docker run -d --rm --name $MY_DOCKER_RANDOM_NAME ${DOCKER_IMAGE}:${DOCKER_IMAGE_ MY_DOCKER_ID=$(docker ps -aqf "name=$MY_DOCKER_RANDOM_NAME") CONTAINER_STATUS=$(docker inspect -f {{.State.Running}} $MY_DOCKER_ID) + if [ $CONTAINER_STATUS = "true" ]; then - echo "Your application working fine" + echo "Your application working fine" + echo "5" else - echo "missing/wrong .Kindly check your docker continer" - exit 1 + echo "missing/wrong .Kindly check your docker continer" + exit 1 fi docker stop $MY_DOCKER_RANDOM_NAME -docker push $DOCKER_IMAGE \ No newline at end of file + +docker_push_image() { + docker push $DOCKER_IMAGE +} + +if docker_push_image; then + echo "Pushing your image" + docker_push_image +else + echo "6" + exit 1 +fi \ No newline at end of file diff --git a/task3/APP/.gitignore b/task3/APP/.gitignore new file mode 100644 index 0000000..a2a3040 --- /dev/null +++ b/task3/APP/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/task3/APP/.mvn/wrapper/MavenWrapperDownloader.java b/task3/APP/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..a45eb6b --- /dev/null +++ b/task3/APP/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/task3/APP/.mvn/wrapper/maven-wrapper.jar b/task3/APP/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 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 EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/task3/APP/.mvn/wrapper/maven-wrapper.properties b/task3/APP/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..642d572 --- /dev/null +++ b/task3/APP/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/task3/APP/docker-compose.yml b/task3/APP/docker-compose.yml new file mode 100644 index 0000000..4ebf5f7 --- /dev/null +++ b/task3/APP/docker-compose.yml @@ -0,0 +1,13 @@ +version: '2' +services: + redis: + image: redis:3.0.3 + hostname: redis-server + ports: + - "6379:6379" + app1: + image: saifnuaimii/app:v1.0.0 + links: + - "redis:redis-server" + ports: + - "8080:8080" \ No newline at end of file diff --git a/task3/APP/mvnw b/task3/APP/mvnw new file mode 100644 index 0000000..a16b543 --- /dev/null +++ b/task3/APP/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/task3/APP/mvnw.cmd b/task3/APP/mvnw.cmd new file mode 100644 index 0000000..c8d4337 --- /dev/null +++ b/task3/APP/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/task3/APP/pom.xml b/task3/APP/pom.xml new file mode 100644 index 0000000..9fafa3f --- /dev/null +++ b/task3/APP/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.4.RELEASE + + + com.webapp + app + 1.0.0-RELEASE + APP + Demo project for Spring Boot + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-data-redis + + + io.lettuce + lettuce-core + + + + + redis.clients + jedis + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/task3/APP/src/main/java/com/webapp/app/AppApplication.java b/task3/APP/src/main/java/com/webapp/app/AppApplication.java new file mode 100644 index 0000000..1b16909 --- /dev/null +++ b/task3/APP/src/main/java/com/webapp/app/AppApplication.java @@ -0,0 +1,13 @@ +package com.webapp.app; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class AppApplication { + + public static void main(String[] args) { + SpringApplication.run(AppApplication.class, args); + } + +} diff --git a/task3/APP/src/main/java/com/webapp/app/config/RedisConfig.java b/task3/APP/src/main/java/com/webapp/app/config/RedisConfig.java new file mode 100644 index 0000000..12621a5 --- /dev/null +++ b/task3/APP/src/main/java/com/webapp/app/config/RedisConfig.java @@ -0,0 +1,30 @@ +package com.webapp.app.config; + + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.listener.ChannelTopic; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.data.redis.serializer.GenericToStringSerializer; + +@Configuration +public class RedisConfig { + + @Bean + JedisConnectionFactory jedisConnectionFactory() { + JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(); + jedisConnectionFactory.getPoolConfig().setMaxIdle(30); + jedisConnectionFactory.getPoolConfig().setMinIdle(10); + return jedisConnectionFactory; + } + + @Bean + public RedisTemplate redisTemplate() { + final RedisTemplate template = new RedisTemplate(); + template.setConnectionFactory(jedisConnectionFactory()); + template.setValueSerializer(new GenericToStringSerializer(Object.class)); + return template; + } +} \ No newline at end of file diff --git a/task3/APP/src/main/java/com/webapp/app/controller/CounterServlet.java b/task3/APP/src/main/java/com/webapp/app/controller/CounterServlet.java new file mode 100644 index 0000000..0a67643 --- /dev/null +++ b/task3/APP/src/main/java/com/webapp/app/controller/CounterServlet.java @@ -0,0 +1,56 @@ +package com.webapp.app.controller; + +import com.webapp.app.model.User; +import com.webapp.app.repository.RedisRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Objects; + + +@RestController +public class CounterServlet extends HttpServlet { + @Autowired + private RedisRepository redisRepository; + private int iHitCounter; + + public void init() throws ServletException { + iHitCounter = 0; + } + + @GetMapping("/") + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + String remoteAddr = ""; + + if (request != null) { + remoteAddr = request.getHeader("X-FORWARDED-FOR"); + if (remoteAddr == null || "".equals(remoteAddr)) { + remoteAddr = request.getRemoteAddr(); + } + User current = redisRepository.findUser(remoteAddr); + User user = new User(remoteAddr, Objects.nonNull(current) ? current.getCount() + 1 : 1); + redisRepository.add(user); + + PrintWriter out = response.getWriter(); + out.println("Hello World !"); + out.println("

Welcome to my website !


"); + out.println("You are visitor number: " + (++iHitCounter)); + out.println("IP number: " + (remoteAddr)); + out.println("count : " + (user.getCount())); + } + + } + + + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + doGet(request, response); + } +} \ No newline at end of file diff --git a/task3/APP/src/main/java/com/webapp/app/model/User.java b/task3/APP/src/main/java/com/webapp/app/model/User.java new file mode 100644 index 0000000..5829c9b --- /dev/null +++ b/task3/APP/src/main/java/com/webapp/app/model/User.java @@ -0,0 +1,41 @@ +package com.webapp.app.model; + +import java.io.Serializable; + +public class User implements Serializable { + + + private static final long serialVersionUID = 1L; + private String id; + private int count; + + public User(String id, int count) { + this.id = id; + this.count = count; + } + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + @Override + public String toString() { + return "User{" + + "id='" + id + '\'' + + ", count=" + count + + '}'; + } +} diff --git a/task3/APP/src/main/java/com/webapp/app/repository/RedisRepository.java b/task3/APP/src/main/java/com/webapp/app/repository/RedisRepository.java new file mode 100644 index 0000000..3186cd3 --- /dev/null +++ b/task3/APP/src/main/java/com/webapp/app/repository/RedisRepository.java @@ -0,0 +1,18 @@ +package com.webapp.app.repository; + + +import com.webapp.app.model.User; + +import java.util.Map; + +public interface RedisRepository { + + Map findAllUsers(); + + void add(User user); + + void delete(String id); + + User findUser(String id); + +} diff --git a/task3/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java b/task3/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java new file mode 100644 index 0000000..02ea8a1 --- /dev/null +++ b/task3/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java @@ -0,0 +1,49 @@ +package com.webapp.app.repository; + +import com.webapp.app.model.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Repository; + +import javax.annotation.PostConstruct; +import java.util.Map; + +@Repository +public class RedisRepositoryImpl implements RedisRepository { + private static final String KEY = "User"; + + private RedisTemplate redisTemplate; + private HashOperations hashOperations; + + @Autowired + public RedisRepositoryImpl(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + @PostConstruct + private void init() { + hashOperations = redisTemplate.opsForHash(); + } + + public void add(final User user) { + hashOperations.put(KEY, user.getId(), user); + } + + public void delete(final String id) { + hashOperations.delete(KEY, id); + } + + public User findUser(final String id) { + Object obj = hashOperations.get(KEY, id); + if (obj instanceof User) + return (User) obj; + return new User("", 0); + } + + public Map findAllUsers() { + return hashOperations.entries(KEY); + } + + +} diff --git a/task3/APP/src/main/resources/application.properties b/task3/APP/src/main/resources/application.properties new file mode 100644 index 0000000..7e42f9d --- /dev/null +++ b/task3/APP/src/main/resources/application.properties @@ -0,0 +1,3 @@ +spring.redis.host=redis-server +spring.redis.port=6379 +server.port=8080 \ No newline at end of file diff --git a/task3/APP/src/main/resources/static/main.js b/task3/APP/src/main/resources/static/main.js new file mode 100644 index 0000000..08c6332 --- /dev/null +++ b/task3/APP/src/main/resources/static/main.js @@ -0,0 +1,138 @@ +/** + * Create HTML table row. + * + * \param text (str) HTML code to be placed inside the row. + */ +function tr(text) { + return '' + text + ''; +} + +/** + * Create HTML table cell element. + * + * \param text (str) The text to be placed inside the cell. + */ +function td(text) { + return '' + text + ''; +} + +/** + * Edit value: load key and value into inputs. + */ +function editButton(key) { + var format = ''; + return format.replace(/{key}/g, key); +} + +/** + * Create HTML button with which a key-value pair is deleted from persistence. + * + * \param key (str) that'll be deleted when the created button is clicked. + */ +function deleteButton(key) { + var format = '' + return format.replace(/{key}/g, key); +} + +/** + * Create HTML table row element. + * + * \param key (str) text into the key cell. + * \param value (str) text into the value cell. + */ +function row(key, value) { + return $( + tr( + td(key) + + td(value) + + td(editButton(key)) + + td(deleteButton(key)))); +} + +/** + * Clear and reload the values in data table. + */ +function refreshTable() { + $.get('/values', function(data) { + var attr, + mainTable = $('#mainTable tbody'); + mainTable.empty(); + for (attr in data) { + if (data.hasOwnProperty(attr)) { + mainTable.append(row(attr, data[attr])); + } + } + }); +} + +function editKey(key) { + /* Find the row with key in first column (key column). */ + var format = '#mainTable tbody td:first-child:contains("{key}")', + selector = format.replace(/{key}/, key), + cells = $(selector).parent().children(), + key = cells[0].textContent, + value = cells[1].textContent, + keyInput = $('#keyInput'), + valueInput = $('#valueInput'); + + /* Load the key and value texts into inputs + * Select value text so it can be directly typed to + */ + keyInput.val(key); + valueInput.val(value); + valueInput.select(); +} + +/** + * Delete key-value pair. + * + * \param key (str) The key to be deleted. + */ +function deleteKey(key) { + /* + * Delete the key. + * Reload keys and values in table to reflect the deleted ones. + * Set keyboard focus to key input: ready to start typing. + */ + $.post('/delete', {key: key}, function() { + refreshTable(); + $('#keyInput').focus(); + }); +} + +$(document).ready(function() { + var keyInput = $('#keyInput'), + valueInput = $('#valueInput'); + + refreshTable(); + $('#addForm').on('submit', function(event) { + var data = { + key: keyInput.val(), + value: valueInput.val() + }; + + /* + * Persist the new key-value pair. + * Clear the inputs. + * Set keyboard focus to key input: ready to start typing. + */ + $.post('/add', data, function() { + refreshTable(); + keyInput.val(''); + valueInput.val(''); + keyInput.focus(); + }); + /* Prevent HTTP form submit */ + event.preventDefault(); + }); + + /* Focus keyboard input into key input; ready to start typing */ + keyInput.focus(); +}); diff --git a/task3/APP/src/main/resources/templates/index.html b/task3/APP/src/main/resources/templates/index.html new file mode 100644 index 0000000..9fdef13 --- /dev/null +++ b/task3/APP/src/main/resources/templates/index.html @@ -0,0 +1,43 @@ + + + +Intro to Redis with Spring Boot + + + + +
+
+
+
+
+ + +
+
+ + +
+ +
+
+
+ + + + + + + + + + +
KeyValue
+
+
+
+ + + + + diff --git a/task3/APP/src/test/java/com/webapp/app/AppApplicationTests.java b/task3/APP/src/test/java/com/webapp/app/AppApplicationTests.java new file mode 100644 index 0000000..60eac84 --- /dev/null +++ b/task3/APP/src/test/java/com/webapp/app/AppApplicationTests.java @@ -0,0 +1,13 @@ +package com.webapp.app; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class AppApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/APP-helm/Chart.yaml b/task5/APP-helm/Chart.yaml similarity index 100% rename from APP-helm/Chart.yaml rename to task5/APP-helm/Chart.yaml diff --git a/APP-helm/app.properties b/task5/APP-helm/app.properties similarity index 100% rename from APP-helm/app.properties rename to task5/APP-helm/app.properties diff --git a/task5/APP-helm/charts/redis/.helmignore b/task5/APP-helm/charts/redis/.helmignore new file mode 100755 index 0000000..b2767ae --- /dev/null +++ b/task5/APP-helm/charts/redis/.helmignore @@ -0,0 +1,3 @@ +.git +# OWNERS file for Kubernetes +OWNERS diff --git a/task5/APP-helm/charts/redis/Chart.yaml b/task5/APP-helm/charts/redis/Chart.yaml new file mode 100755 index 0000000..2bcff9d --- /dev/null +++ b/task5/APP-helm/charts/redis/Chart.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +appVersion: 5.0.5 +description: Open source, advanced key-value store. It is often referred to as a data + structure server since keys can contain strings, hashes, lists, sets and sorted + sets. +engine: gotpl +home: http://redis.io/ +icon: https://bitnami.com/assets/stacks/redis/img/redis-stack-220x234.png +keywords: +- redis +- keyvalue +- database +maintainers: +- email: containers@bitnami.com + name: Bitnami +- email: cedric@desaintmartin.fr + name: desaintmartin +name: redis +sources: +- https://github.com/bitnami/bitnami-docker-redis +version: 9.3.2 diff --git a/task5/APP-helm/charts/redis/README.md b/task5/APP-helm/charts/redis/README.md new file mode 100755 index 0000000..f7aa40b --- /dev/null +++ b/task5/APP-helm/charts/redis/README.md @@ -0,0 +1,463 @@ + +# Redis + +[Redis](http://redis.io/) is an advanced key-value cache and store. It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets, sorted sets, bitmaps and hyperloglogs. + +## TL;DR + +```bash +# Testing configuration +$ helm install stable/redis +``` + +```bash +# Production configuration +$ helm install stable/redis --values values-production.yaml +``` + +## Introduction + +This chart bootstraps a [Redis](https://github.com/bitnami/bitnami-docker-redis) deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +Bitnami charts can be used with [Kubeapps](https://kubeapps.com/) for deployment and management of Helm Charts in clusters. This chart has been tested to work with NGINX Ingress, cert-manager, fluentd and Prometheus on top of the [BKPR](https://kubeprod.io/). + +## Prerequisites + +- Kubernetes 1.12+ +- Helm 2.11+ or Helm 3.0-beta3+ +- PV provisioner support in the underlying infrastructure + +## Installing the Chart + +To install the chart with the release name `my-release`: + +```bash +$ helm install --name my-release stable/redis +``` + +The command deploys Redis on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation. + +> **Tip**: List all releases using `helm list` + +## Uninstalling the Chart + +To uninstall/delete the `my-release` deployment: + +```bash +$ helm delete my-release +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +## Upgrading an existing Release to a new major version + +A major chart version change (like v1.2.3 -> v2.0.0) indicates that there is an +incompatible breaking change needing manual actions. + +### To 8.0.18 + +For releases with `metrics.enabled: true` the default tag for the exporter image is now `v1.x.x`. This introduces many changes including metrics names. You'll want to use [this dashboard](https://github.com/oliver006/redis_exporter/blob/master/contrib/grafana_prometheus_redis_dashboard.json) now. Please see the [redis_exporter github page](https://github.com/oliver006/redis_exporter#upgrading-from-0x-to-1x) for more details. + +### To 7.0.0 + +This version causes a change in the Redis Master StatefulSet definition, so the command helm upgrade would not work out of the box. As an alternative, one of the following could be done: + + - Recommended: Create a clone of the Redis Master PVC (for example, using projects like [this one](https://github.com/edseymour/pvc-transfer)). Then launch a fresh release reusing this cloned PVC. + + ``` + helm install stable/redis --set persistence.existingClaim= + ``` + + - Alternative (not recommended, do at your own risk): `helm delete --purge` does not remove the PVC assigned to the Redis Master StatefulSet. As a consequence, the following commands can be done to upgrade the release + + ``` + helm delete --purge + helm install stable/redis --name + ``` + +Previous versions of the chart were not using persistence in the slaves, so this upgrade would add it to them. Another important change is that no values are inherited from master to slaves. For example, in 6.0.0 `slaves.readinessProbe.periodSeconds`, if empty, would be set to `master.readinessProbe.periodSeconds`. This approach lacked transparency and was difficult to maintain. From now on, all the slave parameters must be configured just as it is done with the masters. + +Some values have changed as well: + + - `master.port` and `slave.port` have been changed to `redisPort` (same value for both master and slaves) + - `master.securityContext` and `slave.securityContext` have been changed to `securityContext`(same values for both master and slaves) + +By default, the upgrade will not change the cluster topology. In case you want to use Redis Sentinel, you must explicitly set `sentinel.enabled` to `true`. + +### To 6.0.0 + +Previous versions of the chart were using an init-container to change the permissions of the volumes. This was done in case the `securityContext` directive in the template was not enough for that (for example, with cephFS). In this new version of the chart, this container is disabled by default (which should not affect most of the deployments). If your installation still requires that init container, execute `helm upgrade` with the `--set volumePermissions.enabled=true`. + +### To 5.0.0 + +The default image in this release may be switched out for any image containing the `redis-server` +and `redis-cli` binaries. If `redis-server` is not the default image ENTRYPOINT, `master.command` +must be specified. + +#### Breaking changes +- `master.args` and `slave.args` are removed. Use `master.command` or `slave.command` instead in order to override the image entrypoint, or `master.extraFlags` to pass additional flags to `redis-server`. +- `disableCommands` is now interpreted as an array of strings instead of a string of comma separated values. +- `master.persistence.path` now defaults to `/data`. + +### 4.0.0 + +This version removes the `chart` label from the `spec.selector.matchLabels` +which is immutable since `StatefulSet apps/v1beta2`. It has been inadvertently +added, causing any subsequent upgrade to fail. See https://github.com/helm/charts/issues/7726. + +It also fixes https://github.com/helm/charts/issues/7726 where a deployment `extensions/v1beta1` can not be upgraded if `spec.selector` is not explicitly set. + +Finally, it fixes https://github.com/helm/charts/issues/7803 by removing mutable labels in `spec.VolumeClaimTemplate.metadata.labels` so that it is upgradable. + +In order to upgrade, delete the Redis StatefulSet before upgrading: +```bash +$ kubectl delete statefulsets.apps --cascade=false my-release-redis-master +``` +And edit the Redis slave (and metrics if enabled) deployment: +```bash +kubectl patch deployments my-release-redis-slave --type=json -p='[{"op": "remove", "path": "/spec/selector/matchLabels/chart"}]' +kubectl patch deployments my-release-redis-metrics --type=json -p='[{"op": "remove", "path": "/spec/selector/matchLabels/chart"}]' +``` + +## Configuration + +The following table lists the configurable parameters of the Redis chart and their default values. + +| Parameter | Description | Default | +|-----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| +| `global.imageRegistry` | Global Docker image registry | `nil` | +| `global.imagePullSecrets` | Global Docker registry secret names as an array | `[]` (does not add image pull secrets to deployed pods) | +| `global.storageClass` | Global storage class for dynamic provisioning | `nil` | +| `image.registry` | Redis Image registry | `docker.io` | +| `image.repository` | Redis Image name | `bitnami/redis` | +| `image.tag` | Redis Image tag | `{TAG_NAME}` | +| `image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `image.pullSecrets` | Specify docker-registry secret names as an array | `nil` | +| `nameOverride` | String to partially override redis.fullname template with a string (will prepend the release name) | `nil` | +| `fullnameOverride` | String to fully override redis.fullname template with a string | `nil` | +| `cluster.enabled` | Use master-slave topology | `true` | +| `cluster.slaveCount` | Number of slaves | `1` | +| `existingSecret` | Name of existing secret object (for password authentication) | `nil` | +| `existingSecretPasswordKey` | Name of key containing password to be retrieved from the existing secret | `nil` | +| `usePassword` | Use password | `true` | +| `usePasswordFile` | Mount passwords as files instead of environment variables | `false` | +| `password` | Redis password (ignored if existingSecret set) | Randomly generated | +| `configmap` | Additional common Redis node configuration (this value is evaluated as a template) | See values.yaml | +| `clusterDomain` | Kubernetes DNS Domain name to use | `cluster.local` | +| `networkPolicy.enabled` | Enable NetworkPolicy | `false` | +| `networkPolicy.allowExternal` | Don't require client label for connections | `true` | +| `securityContext.enabled` | Enable security context (both redis master and slave pods) | `true` | +| `securityContext.fsGroup` | Group ID for the container (both redis master and slave pods) | `1001` | +| `securityContext.runAsUser` | User ID for the container (both redis master and slave pods) | `1001` | +| `securityContext.sysctls` | Set namespaced sysctls for the container (both redis master and slave pods) | `nil` | +| `serviceAccount.create` | Specifies whether a ServiceAccount should be created | `false` | +| `serviceAccount.name` | The name of the ServiceAccount to create | Generated using the fullname template | +| `rbac.create` | Specifies whether RBAC resources should be created | `false` | +| `rbac.role.rules` | Rules to create | `[]` | +| `metrics.enabled` | Start a side-car prometheus exporter | `false` | +| `metrics.image.registry` | Redis exporter image registry | `docker.io` | +| `metrics.image.repository` | Redis exporter image name | `bitnami/redis-exporter` | +| `metrics.image.tag` | Redis exporter image tag | `{TAG_NAME}` | +| `metrics.image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `metrics.image.pullSecrets` | Specify docker-registry secret names as an array | `nil` | +| `metrics.extraArgs` | Extra arguments for the binary; possible values [here](https://github.com/oliver006/redis_exporter#flags) | {} | +| `metrics.podLabels` | Additional labels for Metrics exporter pod | {} | +| `metrics.podAnnotations` | Additional annotations for Metrics exporter pod | {} | +| `metrics.resources` | Exporter resource requests/limit | Memory: `256Mi`, CPU: `100m` | +| `metrics.serviceMonitor.enabled` | if `true`, creates a Prometheus Operator ServiceMonitor (also requires `metrics.enabled` to be `true`) | `false` | +| `metrics.serviceMonitor.namespace` | Optional namespace which Prometheus is running in | `nil` | +| `metrics.serviceMonitor.interval` | How frequently to scrape metrics (use by default, falling back to Prometheus' default) | `nil` | +| `metrics.serviceMonitor.selector` | Default to kube-prometheus install (CoreOS recommended), but should be set according to Prometheus install | `{ prometheus: kube-prometheus }` | +| `metrics.service.type` | Kubernetes Service type (redis metrics) | `ClusterIP` | +| `metrics.service.annotations` | Annotations for the services to monitor (redis master and redis slave service) | {} | +| `metrics.service.labels` | Additional labels for the metrics service | {} | +| `metrics.service.loadBalancerIP` | loadBalancerIP if redis metrics service type is `LoadBalancer` | `nil` | +| `metrics.priorityClassName` | Metrics exporter pod priorityClassName | {} | +| `persistence.existingClaim` | Provide an existing PersistentVolumeClaim | `nil` | +| `master.persistence.enabled` | Use a PVC to persist data (master node) | `true` | +| `master.persistence.path` | Path to mount the volume at, to use other images | `/data` | +| `master.persistence.subPath` | Subdirectory of the volume to mount at | `""` | +| `master.persistence.storageClass` | Storage class of backing PVC | `generic` | +| `master.persistence.accessModes` | Persistent Volume Access Modes | `[ReadWriteOnce]` | +| `master.persistence.size` | Size of data volume | `8Gi` | +| `master.statefulset.updateStrategy` | Update strategy for StatefulSet | onDelete | +| `master.statefulset.rollingUpdatePartition` | Partition update strategy | `nil` | +| `master.podLabels` | Additional labels for Redis master pod | {} | +| `master.podAnnotations` | Additional annotations for Redis master pod | {} | +| `redisPort` | Redis port (in both master and slaves) | `6379` | +| `master.command` | Redis master entrypoint string. The command `redis-server` is executed if this is not provided. | `/run.sh` | +| `master.configmap` | Additional Redis configuration for the master nodes (this value is evaluated as a template) | `nil` | +| `master.disableCommands` | Array of Redis commands to disable (master) | `["FLUSHDB", "FLUSHALL"]` | +| `master.extraFlags` | Redis master additional command line flags | [] | +| `master.nodeSelector` | Redis master Node labels for pod assignment | {"beta.kubernetes.io/arch": "amd64"} | +| `master.tolerations` | Toleration labels for Redis master pod assignment | [] | +| `master.affinity` | Affinity settings for Redis master pod assignment | {} | +| `master.schedulerName` | Name of an alternate scheduler | `nil` | +| `master.service.type` | Kubernetes Service type (redis master) | `ClusterIP` | +| `master.service.port` | Kubernetes Service port (redis master) | `6379` | +| `master.service.nodePort` | Kubernetes Service nodePort (redis master) | `nil` | +| `master.service.annotations` | annotations for redis master service | {} | +| `master.service.labels` | Additional labels for redis master service | {} | +| `master.service.loadBalancerIP` | loadBalancerIP if redis master service type is `LoadBalancer` | `nil` | +| `master.resources` | Redis master CPU/Memory resource requests/limits | Memory: `256Mi`, CPU: `100m` | +| `master.livenessProbe.enabled` | Turn on and off liveness probe (redis master pod) | `true` | +| `master.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated (redis master pod) | `30` | +| `master.livenessProbe.periodSeconds` | How often to perform the probe (redis master pod) | `30` | +| `master.livenessProbe.timeoutSeconds` | When the probe times out (redis master pod) | `5` | +| `master.livenessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis master pod) | `1` | +| `master.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | `5` | +| `master.readinessProbe.enabled` | Turn on and off readiness probe (redis master pod) | `true` | +| `master.readinessProbe.initialDelaySeconds` | Delay before readiness probe is initiated (redis master pod) | `5` | +| `master.readinessProbe.periodSeconds` | How often to perform the probe (redis master pod) | `10` | +| `master.readinessProbe.timeoutSeconds` | When the probe times out (redis master pod) | `1` | +| `master.readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis master pod) | `1` | +| `master.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | `5` | +| `master.priorityClassName` | Redis Master pod priorityClassName | {} | +| `volumePermissions.enabled` | Enable init container that changes volume permissions in the registry (for cases where the default k8s `runAsUser` and `fsUser` values do not work) | `false` | +| `volumePermissions.image.registry` | Init container volume-permissions image registry | `docker.io` | +| `volumePermissions.image.repository` | Init container volume-permissions image name | `bitnami/minideb` | +| `volumePermissions.image.tag` | Init container volume-permissions image tag | `stretch` | +| `volumePermissions.image.pullPolicy` | Init container volume-permissions image pull policy | `Always` | +| `volumePermissions.resources ` | Init container volume-permissions CPU/Memory resource requests/limits | {} | +| `slave.service.type` | Kubernetes Service type (redis slave) | `ClusterIP` | +| `slave.service.nodePort` | Kubernetes Service nodePort (redis slave) | `nil` | +| `slave.service.annotations` | annotations for redis slave service | {} | +| `slave.service.labels` | Additional labels for redis slave service | {} | +| `slave.service.port` | Kubernetes Service port (redis slave) | `6379` | +| `slave.service.loadBalancerIP` | LoadBalancerIP if Redis slave service type is `LoadBalancer` | `nil` | +| `slave.command` | Redis slave entrypoint array. The docker image's ENTRYPOINT is used if this is not provided. | `/run.sh` | +| `slave.configmap` | Additional Redis configuration for the slave nodes (this value is evaluated as a template) | `nil` | +| `slave.disableCommands` | Array of Redis commands to disable (slave) | `[FLUSHDB, FLUSHALL]` | +| `slave.extraFlags` | Redis slave additional command line flags | `[]` | +| `slave.livenessProbe.enabled` | Turn on and off liveness probe (redis slave pod) | `true` | +| `slave.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated (redis slave pod) | `30` | +| `slave.livenessProbe.periodSeconds` | How often to perform the probe (redis slave pod) | `10` | +| `slave.livenessProbe.timeoutSeconds` | When the probe times out (redis slave pod) | `5` | +| `slave.livenessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis slave pod) | `1` | +| `slave.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | `5` | +| `slave.readinessProbe.enabled` | Turn on and off slave.readiness probe (redis slave pod) | `true` | +| `slave.readinessProbe.initialDelaySeconds` | Delay before slave.readiness probe is initiated (redis slave pod) | `5` | +| `slave.readinessProbe.periodSeconds` | How often to perform the probe (redis slave pod) | `10` | +| `slave.readinessProbe.timeoutSeconds` | When the probe times out (redis slave pod) | `10` | +| `slave.readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis slave pod) | `1` | +| `slave.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. (redis slave pod) | `5` | +| `slave.persistence.enabled` | Use a PVC to persist data (slave node) | `true` | +| `slave.persistence.path` | Path to mount the volume at, to use other images | `/data` | +| `slave.persistence.subPath` | Subdirectory of the volume to mount at | `""` | +| `slave.persistence.storageClass` | Storage class of backing PVC | `generic` | +| `slave.persistence.accessModes` | Persistent Volume Access Modes | `[ReadWriteOnce]` | +| `slave.persistence.size` | Size of data volume | `8Gi` | +| `slave.statefulset.updateStrategy` | Update strategy for StatefulSet | onDelete | +| `slave.statefulset.rollingUpdatePartition` | Partition update strategy | `nil` | +| `slave.podLabels` | Additional labels for Redis slave pod | `master.podLabels` | +| `slave.podAnnotations` | Additional annotations for Redis slave pod | `master.podAnnotations` | +| `slave.schedulerName` | Name of an alternate scheduler | `nil` | +| `slave.resources` | Redis slave CPU/Memory resource requests/limits | `{}` | +| `slave.affinity` | Enable node/pod affinity for slaves | {} | +| `slave.priorityClassName` | Redis Slave pod priorityClassName | {} | +| `sentinel.enabled` | Enable sentinel containers | `false` | +| `sentinel.masterSet` | Name of the sentinel master set | `mymaster` | +| `sentinel.initialCheckTimeout` | Timeout for querying the redis sentinel service for the active sentinel list | `5` | +| `sentinel.quorum` | Quorum for electing a new master | `2` | +| `sentinel.downAfterMilliseconds` | Timeout for detecting a Redis node is down | `60000` | +| `sentinel.failoverTimeout` | Timeout for performing a election failover | `18000` | +| `sentinel.parallelSyncs` | Number of parallel syncs in the cluster | `1` | +| `sentinel.port` | Redis Sentinel port | `26379` | +| `sentinel.configmap` | Additional Redis configuration for the sentinel nodes (this value is evaluated as a template) | `nil` | +| `sentinel.service.type` | Kubernetes Service type (redis sentinel) | `ClusterIP` | +| `sentinel.service.nodePort` | Kubernetes Service nodePort (redis sentinel) | `nil` | +| `sentinel.service.annotations` | annotations for redis sentinel service | {} | +| `sentinel.service.labels` | Additional labels for redis sentinel service | {} | +| `sentinel.service.redisPort` | Kubernetes Service port for Redis read only operations | `6379` | +| `sentinel.service.sentinelPort` | Kubernetes Service port for Redis sentinel | `26379` | +| `sentinel.service.redisNodePort` | Kubernetes Service node port for Redis read only operations | `` | +| `sentinel.service.sentinelNodePort` | Kubernetes Service node port for Redis sentinel | `` | +| `sentinel.service.loadBalancerIP` | LoadBalancerIP if Redis sentinel service type is `LoadBalancer` | `nil` | +| `sentinel.livenessProbe.enabled` | Turn on and off liveness probe (redis sentinel pod) | `true` | +| `sentinel.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated (redis sentinel pod) | `5` | +| `sentinel.livenessProbe.periodSeconds` | How often to perform the probe (redis sentinel container) | `5` | +| `sentinel.livenessProbe.timeoutSeconds` | When the probe times out (redis sentinel container) | `5` | +| `sentinel.livenessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis sentinel container) | `1` | +| `sentinel.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | `5` | +| `sentinel.readinessProbe.enabled` | Turn on and off sentinel.readiness probe (redis sentinel pod) | `true` | +| `sentinel.readinessProbe.initialDelaySeconds` | Delay before sentinel.readiness probe is initiated (redis sentinel pod) | `5` | +| `sentinel.readinessProbe.periodSeconds` | How often to perform the probe (redis sentinel pod) | `5` | +| `sentinel.readinessProbe.timeoutSeconds` | When the probe times out (redis sentinel container) | `1` | +| `sentinel.readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis sentinel container) | `1` | +| `sentinel.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. (redis sentinel container) | `5` | +| `sentinel.resources` | Redis sentinel CPU/Memory resource requests/limits | `{}` | +| `sentinel.image.registry` | Redis Sentinel Image registry | `docker.io` | +| `sentinel.image.repository` | Redis Sentinel Image name | `bitnami/redis-sentinel` | +| `sentinel.image.tag` | Redis Sentinel Image tag | `{TAG_NAME}` | +| `sentinel.image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `sentinel.image.pullSecrets` | Specify docker-registry secret names as an array | `nil` | +| `sysctlImage.enabled` | Enable an init container to modify Kernel settings | `false` | +| `sysctlImage.command` | sysctlImage command to execute | [] | +| `sysctlImage.registry` | sysctlImage Init container registry | `docker.io` | +| `sysctlImage.repository` | sysctlImage Init container name | `bitnami/minideb` | +| `sysctlImage.tag` | sysctlImage Init container tag | `stretch` | +| `sysctlImage.pullPolicy` | sysctlImage Init container pull policy | `Always` | +| `sysctlImage.mountHostSys` | Mount the host `/sys` folder to `/host-sys` | `false` | +| `sysctlImage.resources` | sysctlImage Init container CPU/Memory resource requests/limits | {} | + +Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, + +```bash +$ helm install --name my-release \ + --set password=secretpassword \ + stable/redis +``` + +The above command sets the Redis server password to `secretpassword`. + +Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, + +```bash +$ helm install --name my-release -f values.yaml stable/redis +``` + +> **Tip**: You can use the default [values.yaml](values.yaml) + +> **Note for minikube users**: Current versions of minikube (v0.24.1 at the time of writing) provision `hostPath` persistent volumes that are only writable by root. Using chart defaults cause pod failure for the Redis pod as it attempts to write to the `/bitnami` directory. Consider installing Redis with `--set persistence.enabled=false`. See minikube issue [1990](https://github.com/kubernetes/minikube/issues/1990) for more information. + +### Using password file +To use a password file for Redis you need to create a secret containing the password: + +```bash +$ kubectl create secret generic redis-password-file --from-file=/tmp/redis-password +``` +> *NOTE*: It is important that the file with the password must be called `redis-password` + +And deploy the Helm Chart using the secret name: + +```bash +$ helm install stable/redis --set usePassword=true,usePasswordFile=true,existingSecret=redis-password-file,sentinels.enabled=true,metrics.enabled=true +``` + +### Production configuration + +This chart includes a `values-production.yaml` file where you can find some parameters oriented to production configuration in comparison to the regular `values.yaml`. + +```console +$ helm install --name my-release -f ./values-production.yaml stable/redis +``` + +- Number of slaves: +```diff +- cluster.slaveCount: 2 ++ cluster.slaveCount: 3 +``` + +- Enable NetworkPolicy: +```diff +- networkPolicy.enabled: false ++ networkPolicy.enabled: true +``` + +- Start a side-car prometheus exporter: +```diff +- metrics.enabled: false ++ metrics.enabled: true +``` + +### [Rolling VS Immutable tags](https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/) + +It is strongly recommended to use immutable tags in a production environment. This ensures your deployment does not change automatically if the same tag is updated with a different image. + +Bitnami will release a new chart updating its containers if a new version of the main container, significant changes, or critical vulnerabilities exist. + +## NetworkPolicy + +To enable network policy for Redis, install +[a networking plugin that implements the Kubernetes NetworkPolicy spec](https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy#before-you-begin), +and set `networkPolicy.enabled` to `true`. + +For Kubernetes v1.5 & v1.6, you must also turn on NetworkPolicy by setting +the DefaultDeny namespace annotation. Note: this will enforce policy for _all_ pods in the namespace: + + kubectl annotate namespace default "net.beta.kubernetes.io/network-policy={\"ingress\":{\"isolation\":\"DefaultDeny\"}}" + +With NetworkPolicy enabled, only pods with the generated client label will be +able to connect to Redis. This label will be displayed in the output +after a successful install. + +## Persistence + +By default, the chart mounts a [Persistent Volume](http://kubernetes.io/docs/user-guide/persistent-volumes/) at the `/data` path. The volume is created using dynamic volume provisioning. If a Persistent Volume Claim already exists, specify it during installation. + +### Existing PersistentVolumeClaim + +1. Create the PersistentVolume +2. Create the PersistentVolumeClaim +3. Install the chart + +```bash +$ helm install --set persistence.existingClaim=PVC_NAME stable/redis +``` + +## Metrics + +The chart optionally can start a metrics exporter for [prometheus](https://prometheus.io). The metrics endpoint (port 9121) is exposed in the service. Metrics can be scraped from within the cluster using something similar as the described in the [example Prometheus scrape configuration](https://github.com/prometheus/prometheus/blob/master/documentation/examples/prometheus-kubernetes.yml). If metrics are to be scraped from outside the cluster, the Kubernetes API proxy can be utilized to access the endpoint. + +## Host Kernel Settings +Redis may require some changes in the kernel of the host machine to work as expected, in particular increasing the `somaxconn` value and disabling transparent huge pages. +To do so, you can set up a privileged initContainer with the `sysctlImage` config values, for example: +``` +sysctlImage: + enabled: true + mountHostSys: true + command: + - /bin/sh + - -c + - |- + install_packages procps + sysctl -w net.core.somaxconn=10000 + echo never > /host-sys/kernel/mm/transparent_hugepage/enabled +``` + +Alternatively, for Kubernetes 1.12+ you can set `securityContext.sysctls` which will configure sysctls for master and slave pods. Example: + +```yaml +securityContext: + sysctls: + - name: net.core.somaxconn + value: "10000" +``` + +Note that this will not disable transparent huge tables. + +## Cluster topologies + +### Default: Master-Slave + +When installing the chart with `cluster.enabled=true`, it will deploy a Redis master StatefulSet (only one master node allowed) and a Redis slave StatefulSet. The slaves will be read-replicas of the master. Two services will be exposed: + + - Redis Master service: Points to the master, where read-write operations can be performed + - Redis Slave service: Points to the slaves, where only read operations are allowed. + +In case the master crashes, the slaves will wait until the master node is respawned again by the Kubernetes Controller Manager. + +### Master-Slave with Sentinel + +When installing the chart with `cluster.enabled=true` and `sentinel.enabled=true`, it will deploy a Redis master StatefulSet (only one master allowed) and a Redis slave StatefulSet. In this case, the pods will contain en extra container with Redis Sentinel. This container will form a cluster of Redis Sentinel nodes, which will promote a new master in case the actual one fails. In addition to this, only one service is exposed: + + - Redis service: Exposes port 6379 for Redis read-only operations and port 26379 for accesing Redis Sentinel. + +For read-only operations, access the service using port 6379. For write operations, it's necessary to access the Redis Sentinel cluster and query the current master using the command below (using redis-cli or similar: + +``` +SENTINEL get-master-addr-by-name +``` +This command will return the address of the current master, which can be accessed from inside the cluster. + +In case the current master crashes, the Sentinel containers will elect a new master node. + +## Notable changes + +### 9.0.0 +The metrics exporter has been changed from a separate deployment to a sidecar container, due to the latest changes in the Redis exporter code. Check the [official page](https://github.com/oliver006/redis_exporter/) for more information. The metrics container image was changed from oliver006/redis_exporter to bitnami/redis-exporter (Bitnami's maintained package of oliver006/redis_exporter). + +### 7.0.0 +In order to improve the performance in case of slave failure, we added persistence to the read-only slaves. That means that we moved from Deployment to StatefulSets. This should not affect upgrades from previous versions of the chart, as the deployments did not contain any persistence at all. + +This version also allows enabling Redis Sentinel containers inside of the Redis Pods (feature disabled by default). In case the master crashes, a new Redis node will be elected as master. In order to query the current master (no redis master service is exposed), you need to query first the Sentinel cluster. Find more information [in this section](#master-slave-with-sentinel). diff --git a/task5/APP-helm/charts/redis/ci/default-values.yaml b/task5/APP-helm/charts/redis/ci/default-values.yaml new file mode 100755 index 0000000..fc2ba60 --- /dev/null +++ b/task5/APP-helm/charts/redis/ci/default-values.yaml @@ -0,0 +1 @@ +# Leave this file empty to ensure that CI runs builds against the default configuration in values.yaml. diff --git a/task5/APP-helm/charts/redis/ci/dev-values.yaml b/task5/APP-helm/charts/redis/ci/dev-values.yaml new file mode 100755 index 0000000..be01913 --- /dev/null +++ b/task5/APP-helm/charts/redis/ci/dev-values.yaml @@ -0,0 +1,9 @@ +master: + persistence: + enabled: false + +cluster: + enabled: true + slaveCount: 1 + +usePassword: false diff --git a/task5/APP-helm/charts/redis/ci/extra-flags-values.yaml b/task5/APP-helm/charts/redis/ci/extra-flags-values.yaml new file mode 100755 index 0000000..71132f7 --- /dev/null +++ b/task5/APP-helm/charts/redis/ci/extra-flags-values.yaml @@ -0,0 +1,11 @@ +master: + extraFlags: + - --maxmemory-policy allkeys-lru + persistence: + enabled: false +slave: + extraFlags: + - --maxmemory-policy allkeys-lru + persistence: + enabled: false +usePassword: false diff --git a/task5/APP-helm/charts/redis/ci/production-sentinel-values.yaml b/task5/APP-helm/charts/redis/ci/production-sentinel-values.yaml new file mode 100755 index 0000000..c230a56 --- /dev/null +++ b/task5/APP-helm/charts/redis/ci/production-sentinel-values.yaml @@ -0,0 +1,521 @@ +## Global Docker image parameters +## Please, note that this will override the image parameters, including dependencies, configured to use the global value +## Current available global Docker image parameters: imageRegistry and imagePullSecrets +## +# global: +# imageRegistry: myRegistryName +# imagePullSecrets: +# - myRegistryKeySecretName + +## Bitnami Redis image version +## ref: https://hub.docker.com/r/bitnami/redis/tags/ +## +image: + registry: docker.io + repository: bitnami/redis + ## Bitnami Redis image tag + ## ref: https://github.com/bitnami/bitnami-docker-redis#supported-tags-and-respective-dockerfile-links + ## + tag: 5.0.5-debian-9-r36 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + +## Redis pod Security Context +securityContext: + enabled: true + fsGroup: 1001 + runAsUser: 1001 + +## Cluster settings +cluster: + enabled: true + slaveCount: 3 + +## Use redis sentinel in the redis pod. This will disable the master and slave services and +## create one redis service with ports to the sentinel and the redis instances +sentinel: + enabled: true + ## Bitnami Redis Sentintel image version + ## ref: https://hub.docker.com/r/bitnami/redis-sentinel/tags/ + ## + image: + registry: docker.io + repository: bitnami/redis-sentinel + ## Bitnami Redis image tag + ## ref: https://github.com/bitnami/bitnami-docker-redis-sentinel#supported-tags-and-respective-dockerfile-links + ## + tag: 5.0.5-debian-9-r37 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + masterSet: mymaster + initialCheckTimeout: 5 + quorum: 2 + downAfterMilliseconds: 60000 + failoverTimeout: 18000 + parallelSyncs: 1 + port: 26379 + ## Configure extra options for Redis Sentinel liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 5 + ## Redis Sentinel resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + ## Redis Sentinel Service properties + service: + ## Redis Sentinel Service type + type: ClusterIP + sentinelPort: 26379 + redisPort: 6379 + + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # sentinelNodePort: + # redisNodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + loadBalancerIP: + +networkPolicy: + ## Specifies whether a NetworkPolicy should be created + ## + enabled: true + + ## The Policy model to apply. When set to false, only pods with the correct + ## client label will have network access to the port Redis is listening + ## on. When true, Redis will accept connections from any source + ## (with the correct destination port). + ## + # allowExternal: true + +serviceAccount: + ## Specifies whether a ServiceAccount should be created + ## + create: false + ## The name of the ServiceAccount to use. + ## If not set and create is true, a name is generated using the fullname template + name: + +rbac: + ## Specifies whether RBAC resources should be created + ## + create: false + + role: + ## Rules to create. It follows the role specification + # rules: + # - apiGroups: + # - extensions + # resources: + # - podsecuritypolicies + # verbs: + # - use + # resourceNames: + # - gce.unprivileged + rules: [] + + +## Use password authentication +usePassword: true +## Redis password (both master and slave) +## Defaults to a random 10-character alphanumeric string if not set and usePassword is true +## ref: https://github.com/bitnami/bitnami-docker-redis#setting-the-server-password-on-first-run +## +password: +## Use existing secret (ignores previous password) +# existingSecret: +## Password key to be retrieved from Redis secret +## +# existingSecretPasswordKey: + +## Mount secrets as files instead of environment variables +usePasswordFile: false + +## Persist data to a persistent volume +persistence: {} + ## A manually managed Persistent Volume and Claim + ## Requires persistence.enabled: true + ## If defined, PVC must be created manually before volume will be bound + # existingClaim: + +# Redis port +redisPort: 6379 + +## +## Redis Master parameters +## +master: + ## Redis command arguments + ## + ## Can be used to specify command line arguments, for example: + ## + command: "/run.sh" + ## Redis additional command line flags + ## + ## Can be used to specify command line flags, for example: + ## + ## extraFlags: + ## - "--maxmemory-policy volatile-ttl" + ## - "--repl-backlog-size 1024mb" + extraFlags: [] + ## Comma-separated list of Redis commands to disable + ## + ## Can be used to disable Redis commands for security reasons. + ## Commands will be completely disabled by renaming each to an empty string. + ## ref: https://redis.io/topics/security#disabling-of-specific-commands + ## + disableCommands: + - FLUSHDB + - FLUSHALL + + ## Redis Master additional pod labels and annotations + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + podLabels: {} + podAnnotations: {} + + ## Redis Master resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + + ## Configure extra options for Redis Master liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 5 + + ## Redis Master Node selectors and tolerations for pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#taints-and-tolerations-beta-feature + ## + # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} + # tolerations: [] + ## Redis Master pod/node affinity/anti-affinity + ## + affinity: {} + + ## Redis Master Service properties + service: + ## Redis Master Service type + type: ClusterIP + port: 6379 + + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # nodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + loadBalancerIP: + + ## Enable persistence using Persistent Volume Claims + ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + persistence: + enabled: true + ## The path the volume will be mounted at, useful when using different + ## Redis images. + path: /data + ## The subdirectory of the volume to mount to, useful in dev environments + ## and one PV for multiple services. + subPath: "" + ## redis data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + accessModes: + - ReadWriteOnce + size: 8Gi + + ## Update strategy, can be set to RollingUpdate or onDelete by default. + ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets + statefulset: + updateStrategy: RollingUpdate + ## Partition update strategy + ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions + # rollingUpdatePartition: + + ## Redis Master pod priorityClassName + # priorityClassName: {} + + +## +## Redis Slave properties +## Note: service.type is a mandatory parameter +## The rest of the parameters are either optional or, if undefined, will inherit those declared in Redis Master +## +slave: + ## Slave Service properties + service: + ## Redis Slave Service type + type: ClusterIP + ## Redis port + port: 6379 + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # nodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + loadBalancerIP: + + ## Redis slave port + port: 6379 + + ## Can be used to specify command line arguments, for example: + ## + command: "/run.sh" + ## Redis extra flags + extraFlags: [] + ## List of Redis commands to disable + disableCommands: + - FLUSHDB + - FLUSHALL + + ## Redis Slave pod/node affinity/anti-affinity + ## + affinity: {} + + ## Configure extra options for Redis Slave liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 10 + successThreshold: 1 + failureThreshold: 5 + + ## Redis slave Resource + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + + ## Enable persistence using Persistent Volume Claims + ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + persistence: + enabled: true + ## The path the volume will be mounted at, useful when using different + ## Redis images. + path: /data + ## The subdirectory of the volume to mount to, useful in dev environments + ## and one PV for multiple services. + subPath: "" + ## redis data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + accessModes: + - ReadWriteOnce + size: 8Gi + + ## Update strategy, can be set to RollingUpdate or onDelete by default. + ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets + statefulset: + updateStrategy: RollingUpdate + ## Partition update strategy + ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions + # rollingUpdatePartition: + + ## Redis slave selectors and tolerations for pod assignment + # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} + # tolerations: [] + + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + + ## Redis slave pod Annotation and Labels + podLabels: {} + podAnnotations: {} + + ## Redis slave pod priorityClassName + # priorityClassName: {} + +## Prometheus Exporter / Metrics +## +metrics: + enabled: true + + image: + registry: docker.io + repository: bitnami/redis-exporter + tag: 1.0.3-debian-9-r0 + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + + ## Metrics exporter resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + # resources: {} + ## Metrics exporter pod priorityClassName + # priorityClassName: {} + service: + type: ClusterIP + ## Use serviceLoadBalancerIP to request a specific static IP, + ## otherwise leave blank + # loadBalancerIP: + annotations: {} + + ## Extra arguments for Metrics exporter, for example: + ## extraArgs: + ## check-keys: myKey,myOtherKey + # extraArgs: {} + + ## Metrics exporter pod Annotation and Labels + podAnnotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9121" + # podLabels: {} + + # Enable this if you're using https://github.com/coreos/prometheus-operator + serviceMonitor: + enabled: false + ## Specify a namespace if needed + # namespace: monitoring + # fallback to the prometheus default unless specified + # interval: 10s + ## Defaults to what's used if you follow CoreOS [Prometheus Install Instructions](https://github.com/helm/charts/tree/master/stable/prometheus-operator#tldr) + ## [Prometheus Selector Label](https://github.com/helm/charts/tree/master/stable/prometheus-operator#prometheus-operator-1) + ## [Kube Prometheus Selector Label](https://github.com/helm/charts/tree/master/stable/prometheus-operator#exporters) + selector: + prometheus: kube-prometheus +## +## Init containers parameters: +## volumePermissions: Change the owner of the persist volume mountpoint to RunAsUser:fsGroup +## +volumePermissions: + enabled: false + image: + registry: docker.io + repository: bitnami/minideb + tag: stretch + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + resources: {} + # resources: + # requests: + # memory: 128Mi + # cpu: 100m + +## Redis config file +## ref: https://redis.io/topics/config +## +configmap: |- + # maxmemory-policy volatile-lru + +## Sysctl InitContainer +## used to perform sysctl operation to modify Kernel settings (needed sometimes to avoid warnings) +sysctlImage: + enabled: false + command: [] + registry: docker.io + repository: bitnami/minideb + tag: stretch + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + mountHostSys: false + resources: {} + # resources: + # requests: + # memory: 128Mi + # cpu: 100m diff --git a/task5/APP-helm/charts/redis/ci/production-values.yaml b/task5/APP-helm/charts/redis/ci/production-values.yaml new file mode 100755 index 0000000..29f7153 --- /dev/null +++ b/task5/APP-helm/charts/redis/ci/production-values.yaml @@ -0,0 +1,522 @@ +## Global Docker image parameters +## Please, note that this will override the image parameters, including dependencies, configured to use the global value +## Current available global Docker image parameters: imageRegistry and imagePullSecrets +## +# global: +# imageRegistry: myRegistryName +# imagePullSecrets: +# - myRegistryKeySecretName + +## Bitnami Redis image version +## ref: https://hub.docker.com/r/bitnami/redis/tags/ +## +image: + registry: docker.io + repository: bitnami/redis + ## Bitnami Redis image tag + ## ref: https://github.com/bitnami/bitnami-docker-redis#supported-tags-and-respective-dockerfile-links + ## + tag: 5.0.5-debian-9-r36 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + +## Redis pod Security Context +securityContext: + enabled: true + fsGroup: 1001 + runAsUser: 1001 + +## Cluster settings +cluster: + enabled: true + slaveCount: 3 + +## Use redis sentinel in the redis pod. This will disable the master and slave services and +## create one redis service with ports to the sentinel and the redis instances +sentinel: + enabled: false + ## Bitnami Redis Sentintel image version + ## ref: https://hub.docker.com/r/bitnami/redis-sentinel/tags/ + ## + image: + registry: docker.io + repository: bitnami/redis-sentinel + ## Bitnami Redis image tag + ## ref: https://github.com/bitnami/bitnami-docker-redis-sentinel#supported-tags-and-respective-dockerfile-links + ## + tag: 5.0.5-debian-9-r37 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + masterSet: mymaster + initialCheckTimeout: 5 + quorum: 2 + downAfterMilliseconds: 60000 + failoverTimeout: 18000 + parallelSyncs: 1 + port: 26379 + ## Configure extra options for Redis Sentinel liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 5 + ## Redis Sentinel resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + ## Redis Sentinel Service properties + service: + ## Redis Sentinel Service type + type: ClusterIP + sentinelPort: 26379 + redisPort: 6379 + + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # sentinelNodePort: + # redisNodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + loadBalancerIP: + +networkPolicy: + ## Specifies whether a NetworkPolicy should be created + ## + enabled: true + + ## The Policy model to apply. When set to false, only pods with the correct + ## client label will have network access to the port Redis is listening + ## on. When true, Redis will accept connections from any source + ## (with the correct destination port). + ## + # allowExternal: true + +serviceAccount: + ## Specifies whether a ServiceAccount should be created + ## + create: false + ## The name of the ServiceAccount to use. + ## If not set and create is true, a name is generated using the fullname template + name: + +rbac: + ## Specifies whether RBAC resources should be created + ## + create: false + + role: + ## Rules to create. It follows the role specification + # rules: + # - apiGroups: + # - extensions + # resources: + # - podsecuritypolicies + # verbs: + # - use + # resourceNames: + # - gce.unprivileged + rules: [] + + +## Use password authentication +usePassword: true +## Redis password (both master and slave) +## Defaults to a random 10-character alphanumeric string if not set and usePassword is true +## ref: https://github.com/bitnami/bitnami-docker-redis#setting-the-server-password-on-first-run +## +password: +## Use existing secret (ignores previous password) +# existingSecret: +## Password key to be retrieved from Redis secret +## +# existingSecretPasswordKey: + +## Mount secrets as files instead of environment variables +usePasswordFile: false + +## Persist data to a persistent volume +persistence: {} + ## A manually managed Persistent Volume and Claim + ## Requires persistence.enabled: true + ## If defined, PVC must be created manually before volume will be bound + # existingClaim: + +# Redis port +redisPort: 6379 + +## +## Redis Master parameters +## +master: + ## Redis command arguments + ## + ## Can be used to specify command line arguments, for example: + ## + command: "/run.sh" + ## Redis additional command line flags + ## + ## Can be used to specify command line flags, for example: + ## + ## extraFlags: + ## - "--maxmemory-policy volatile-ttl" + ## - "--repl-backlog-size 1024mb" + extraFlags: [] + ## Comma-separated list of Redis commands to disable + ## + ## Can be used to disable Redis commands for security reasons. + ## Commands will be completely disabled by renaming each to an empty string. + ## ref: https://redis.io/topics/security#disabling-of-specific-commands + ## + disableCommands: + - FLUSHDB + - FLUSHALL + + ## Redis Master additional pod labels and annotations + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + podLabels: {} + podAnnotations: {} + + ## Redis Master resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + + ## Configure extra options for Redis Master liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 5 + + ## Redis Master Node selectors and tolerations for pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#taints-and-tolerations-beta-feature + ## + # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} + # tolerations: [] + ## Redis Master pod/node affinity/anti-affinity + ## + affinity: {} + + ## Redis Master Service properties + service: + ## Redis Master Service type + type: ClusterIP + port: 6379 + + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # nodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + loadBalancerIP: + + ## Enable persistence using Persistent Volume Claims + ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + persistence: + enabled: true + ## The path the volume will be mounted at, useful when using different + ## Redis images. + path: /data + ## The subdirectory of the volume to mount to, useful in dev environments + ## and one PV for multiple services. + subPath: "" + ## redis data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + accessModes: + - ReadWriteOnce + size: 8Gi + + ## Update strategy, can be set to RollingUpdate or onDelete by default. + ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets + statefulset: + updateStrategy: RollingUpdate + ## Partition update strategy + ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions + # rollingUpdatePartition: + + ## Redis Master pod priorityClassName + # priorityClassName: {} + + +## +## Redis Slave properties +## Note: service.type is a mandatory parameter +## The rest of the parameters are either optional or, if undefined, will inherit those declared in Redis Master +## +slave: + ## Slave Service properties + service: + ## Redis Slave Service type + type: ClusterIP + ## Redis port + port: 6379 + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # nodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + loadBalancerIP: + + ## Redis slave port + port: 6379 + + ## Can be used to specify command line arguments, for example: + ## + command: "/run.sh" + ## Redis extra flags + extraFlags: [] + ## List of Redis commands to disable + disableCommands: + - FLUSHDB + - FLUSHALL + + ## Redis Slave pod/node affinity/anti-affinity + ## + affinity: {} + + ## Configure extra options for Redis Slave liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 10 + successThreshold: 1 + failureThreshold: 5 + + ## Redis slave Resource + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + + ## Enable persistence using Persistent Volume Claims + ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + persistence: + enabled: true + ## The path the volume will be mounted at, useful when using different + ## Redis images. + path: /data + ## The subdirectory of the volume to mount to, useful in dev environments + ## and one PV for multiple services. + subPath: "" + ## redis data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + accessModes: + - ReadWriteOnce + size: 8Gi + + ## Update strategy, can be set to RollingUpdate or onDelete by default. + ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets + statefulset: + updateStrategy: RollingUpdate + ## Partition update strategy + ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions + # rollingUpdatePartition: + + ## Redis slave selectors and tolerations for pod assignment + # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} + # tolerations: [] + + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + + ## Redis slave pod Annotation and Labels + podLabels: {} + podAnnotations: {} + + ## Redis slave pod priorityClassName + # priorityClassName: {} + +## Prometheus Exporter / Metrics +## +metrics: + enabled: true + + image: + registry: docker.io + repository: bitnami/redis-exporter + tag: 1.0.3-debian-9-r0 + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + + ## Metrics exporter resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + # resources: {} + + ## Extra arguments for Metrics exporter, for example: + ## extraArgs: + ## check-keys: myKey,myOtherKey + # extraArgs: {} + ## Metrics exporter pod priorityClassName + # priorityClassName: {} + service: + type: ClusterIP + ## Use serviceLoadBalancerIP to request a specific static IP, + ## otherwise leave blank + # loadBalancerIP: + annotations: {} + + ## Metrics exporter pod Annotation and Labels + podAnnotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9121" + # podLabels: {} + + # Enable this if you're using https://github.com/coreos/prometheus-operator + serviceMonitor: + enabled: false + ## Specify a namespace if needed + # namespace: monitoring + # fallback to the prometheus default unless specified + # interval: 10s + ## Defaults to what's used if you follow CoreOS [Prometheus Install Instructions](https://github.com/helm/charts/tree/master/stable/prometheus-operator#tldr) + ## [Prometheus Selector Label](https://github.com/helm/charts/tree/master/stable/prometheus-operator#prometheus-operator-1) + ## [Kube Prometheus Selector Label](https://github.com/helm/charts/tree/master/stable/prometheus-operator#exporters) + selector: + prometheus: kube-prometheus + +## +## Init containers parameters: +## volumePermissions: Change the owner of the persist volume mountpoint to RunAsUser:fsGroup +## +volumePermissions: + enabled: false + image: + registry: docker.io + repository: bitnami/minideb + tag: stretch + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + resources: {} + # resources: + # requests: + # memory: 128Mi + # cpu: 100m + +## Redis config file +## ref: https://redis.io/topics/config +## +configmap: |- + # maxmemory-policy volatile-lru + +## Sysctl InitContainer +## used to perform sysctl operation to modify Kernel settings (needed sometimes to avoid warnings) +sysctlImage: + enabled: false + command: [] + registry: docker.io + repository: bitnami/minideb + tag: stretch + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + mountHostSys: false + resources: {} + # resources: + # requests: + # memory: 128Mi + # cpu: 100m diff --git a/task5/APP-helm/charts/redis/ci/redis-lib-values.yaml b/task5/APP-helm/charts/redis/ci/redis-lib-values.yaml new file mode 100755 index 0000000..e03382b --- /dev/null +++ b/task5/APP-helm/charts/redis/ci/redis-lib-values.yaml @@ -0,0 +1,13 @@ +## Redis library image +## ref: https://hub.docker.com/r/library/redis/ +## +image: + registry: docker.io + repository: redis + tag: '5.0.5' + +master: + command: "redis-server" + +slave: + command: "redis-server" diff --git a/task5/APP-helm/charts/redis/ci/redisgraph-module-values.yaml b/task5/APP-helm/charts/redis/ci/redisgraph-module-values.yaml new file mode 100755 index 0000000..8096020 --- /dev/null +++ b/task5/APP-helm/charts/redis/ci/redisgraph-module-values.yaml @@ -0,0 +1,10 @@ +image: + registry: docker.io + repository: redislabs/redisgraph + tag: '1.0.0' + +master: + command: "redis-server" + +slave: + command: "redis-server" diff --git a/task5/APP-helm/charts/redis/templates/NOTES.txt b/task5/APP-helm/charts/redis/templates/NOTES.txt new file mode 100755 index 0000000..f3b46e1 --- /dev/null +++ b/task5/APP-helm/charts/redis/templates/NOTES.txt @@ -0,0 +1,104 @@ +** Please be patient while the chart is being deployed ** + +{{- if contains .Values.master.service.type "LoadBalancer" }} +{{- if not .Values.usePassword }} +{{ if and (not .Values.networkPolicy.enabled) (.Values.networkPolicy.allowExternal) }} + +------------------------------------------------------------------------------- + WARNING + + By specifying "master.service.type=LoadBalancer" and "usePassword=false" you have + most likely exposed the Redis service externally without any authentication + mechanism. + + For security reasons, we strongly suggest that you switch to "ClusterIP" or + "NodePort". As alternative, you can also switch to "usePassword=true" + providing a valid password on "password" parameter. + +------------------------------------------------------------------------------- +{{- end }} +{{- end }} +{{- end }} + +{{- if .Values.cluster.enabled }} +{{- if .Values.sentinel.enabled }} +Redis can be accessed via port {{ .Values.sentinel.service.redisPort }} on the following DNS name from within your cluster: + +{{ template "redis.fullname" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} for read only operations + +For read/write operations, first access the Redis Sentinel cluster, which is available in port {{ .Values.sentinel.service.sentinelPort }} using the same domain name above. + +{{- else }} +Redis can be accessed via port {{ .Values.redisPort }} on the following DNS names from within your cluster: + +{{ template "redis.fullname" . }}-master.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} for read/write operations +{{ template "redis.fullname" . }}-slave.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} for read-only operations +{{- end }} + +{{- else }} +Redis can be accessed via port {{ .Values.redisPort }} on the following DNS name from within your cluster: + +{{ template "redis.fullname" . }}-master.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} + +{{- end }} + +{{ if .Values.usePassword }} +To get your password run: + + export REDIS_PASSWORD=$(kubectl get secret --namespace {{ .Release.Namespace }} {{ template "redis.secretName" . }} -o jsonpath="{.data.redis-password}" | base64 --decode) +{{- end }} + +To connect to your Redis server: + +1. Run a Redis pod that you can use as a client: + + kubectl run --namespace {{ .Release.Namespace }} {{ template "redis.fullname" . }}-client --rm --tty -i --restart='Never' \ + {{ if .Values.usePassword }} --env REDIS_PASSWORD=$REDIS_PASSWORD \{{ end }} + {{- if and (.Values.networkPolicy.enabled) (not .Values.networkPolicy.allowExternal) }}--labels="{{ template "redis.name" . }}-client=true" \{{- end }} + --image {{ template "redis.image" . }} -- bash + +2. Connect using the Redis CLI: + +{{- if .Values.cluster.enabled }} + {{- if .Values.sentinel.enabled }} + redis-cli -h {{ template "redis.fullname" . }} -p {{ .Values.sentinel.service.redisPort }}{{ if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }} # Read only operations + redis-cli -h {{ template "redis.fullname" . }} -p {{ .Values.sentinel.service.sentinelPort }}{{ if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }} # Sentinel access + {{- else }} + redis-cli -h {{ template "redis.fullname" . }}-master{{ if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }} + redis-cli -h {{ template "redis.fullname" . }}-slave{{ if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }} + {{- end }} +{{- else }} + redis-cli -h {{ template "redis.fullname" . }}-master{{ if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }} +{{- end }} + +{{ if and (.Values.networkPolicy.enabled) (not .Values.networkPolicy.allowExternal) }} +Note: Since NetworkPolicy is enabled, only pods with label +{{ template "redis.fullname" . }}-client=true" +will be able to connect to redis. +{{- else -}} + +To connect to your database from outside the cluster execute the following commands: + +{{- if contains "NodePort" .Values.master.service.type }} + + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "redis.fullname" . }}-master) + redis-cli -h $NODE_IP -p $NODE_PORT {{- if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }} + +{{- else if contains "LoadBalancer" .Values.master.service.type }} + + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + Watch the status with: 'kubectl get svc --namespace {{ .Release.Namespace }} -w {{ template "redis.fullname" . }}' + + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "redis.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + redis-cli -h $SERVICE_IP -p {{ .Values.master.service.nodePort }} {{- if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }} + +{{- else if contains "ClusterIP" .Values.master.service.type }} + + kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ template "redis.fullname" . }}-master {{ .Values.redisPort }}:{{ .Values.redisPort }} & + redis-cli -h 127.0.0.1 -p {{ .Values.redisPort }} {{- if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }} + +{{- end }} +{{- end }} + +{{ include "redis.checkRollingTags" . }} diff --git a/task5/APP-helm/charts/redis/templates/_helpers.tpl b/task5/APP-helm/charts/redis/templates/_helpers.tpl new file mode 100755 index 0000000..be47dd8 --- /dev/null +++ b/task5/APP-helm/charts/redis/templates/_helpers.tpl @@ -0,0 +1,320 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "redis.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Expand the chart plus release name (used by the chart label) +*/}} +{{- define "redis.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "redis.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for networkpolicy. +*/}} +{{- define "networkPolicy.apiVersion" -}} +{{- if semverCompare ">=1.4-0, <1.7-0" .Capabilities.KubeVersion.GitVersion -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper Redis image name +*/}} +{{- define "redis.image" -}} +{{- $registryName := .Values.image.registry -}} +{{- $repositoryName := .Values.image.repository -}} +{{- $tag := .Values.image.tag | toString -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. +Also, we can't use a single if because lazy evaluation is not an option +*/}} +{{- if .Values.global }} + {{- if .Values.global.imageRegistry }} + {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} + {{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} + {{- end -}} +{{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper Redis Sentinel image name +*/}} +{{- define "sentinel.image" -}} +{{- $registryName := .Values.sentinel.image.registry -}} +{{- $repositoryName := .Values.sentinel.image.repository -}} +{{- $tag := .Values.sentinel.image.tag | toString -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. +Also, we can't use a single if because lazy evaluation is not an option +*/}} +{{- if .Values.global }} + {{- if .Values.global.imageRegistry }} + {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} + {{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} + {{- end -}} +{{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper image name (for the metrics image) +*/}} +{{- define "redis.metrics.image" -}} +{{- $registryName := .Values.metrics.image.registry -}} +{{- $repositoryName := .Values.metrics.image.repository -}} +{{- $tag := .Values.metrics.image.tag | toString -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. +Also, we can't use a single if because lazy evaluation is not an option +*/}} +{{- if .Values.global }} + {{- if .Values.global.imageRegistry }} + {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} + {{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} + {{- end -}} +{{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper image name (for the init container volume-permissions image) +*/}} +{{- define "redis.volumePermissions.image" -}} +{{- $registryName := .Values.volumePermissions.image.registry -}} +{{- $repositoryName := .Values.volumePermissions.image.repository -}} +{{- $tag := .Values.volumePermissions.image.tag | toString -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. +Also, we can't use a single if because lazy evaluation is not an option +*/}} +{{- if .Values.global }} + {{- if .Values.global.imageRegistry }} + {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} + {{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} + {{- end -}} +{{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "redis.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "redis.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Get the password secret. +*/}} +{{- define "redis.secretName" -}} +{{- if .Values.existingSecret -}} +{{- printf "%s" .Values.existingSecret -}} +{{- else -}} +{{- printf "%s" (include "redis.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Get the password key to be retrieved from Redis secret. +*/}} +{{- define "redis.secretPasswordKey" -}} +{{- if and .Values.existingSecret .Values.existingSecretPasswordKey -}} +{{- printf "%s" .Values.existingSecretPasswordKey -}} +{{- else -}} +{{- printf "redis-password" -}} +{{- end -}} +{{- end -}} + +{{/* +Return sysctl image +*/}} +{{- define "redis.sysctl.image" -}} +{{- $registryName := default "docker.io" .Values.sysctlImage.registry -}} +{{- $repositoryName := .Values.sysctlImage.repository -}} +{{- $tag := default "stretch" .Values.sysctlImage.tag | toString -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. +Also, we can't use a single if because lazy evaluation is not an option +*/}} +{{- if .Values.global }} + {{- if .Values.global.imageRegistry }} + {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} + {{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} + {{- end -}} +{{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names +*/}} +{{- define "redis.imagePullSecrets" -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 does not support it, so we need to implement this if-else logic. +Also, we can not use a single if because lazy evaluation is not an option +*/}} +{{- if .Values.global }} +{{- if .Values.global.imagePullSecrets }} +imagePullSecrets: +{{- range .Values.global.imagePullSecrets }} + - name: {{ . }} +{{- end }} +{{- else if or .Values.image.pullSecrets .Values.metrics.image.pullSecrets .Values.sysctlImage.pullSecrets .Values.volumePermissions.image.pullSecrets }} +imagePullSecrets: +{{- range .Values.image.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- range .Values.metrics.image.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- range .Values.sysctlImage.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- range .Values.volumePermissions.image.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- end -}} +{{- else if or .Values.image.pullSecrets .Values.metrics.image.pullSecrets .Values.sysctlImage.pullSecrets .Values.volumePermissions.image.pullSecrets }} +imagePullSecrets: +{{- range .Values.image.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- range .Values.metrics.image.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- range .Values.sysctlImage.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- range .Values.volumePermissions.image.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- end -}} +{{- end -}} + +{{/* Check if there are rolling tags in the images */}} +{{- define "redis.checkRollingTags" -}} +{{- if and (contains "bitnami/" .Values.image.repository) (not (.Values.image.tag | toString | regexFind "-r\\d+$|sha256:")) }} +WARNING: Rolling tag detected ({{ .Values.image.repository }}:{{ .Values.image.tag }}), please note that it is strongly recommended to avoid using rolling tags in a production environment. ++info https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/ +{{- end }} +{{- if and (contains "bitnami/" .Values.sentinel.image.repository) (not (.Values.sentinel.image.tag | toString | regexFind "-r\\d+$|sha256:")) }} +WARNING: Rolling tag detected ({{ .Values.sentinel.image.repository }}:{{ .Values.sentinel.image.tag }}), please note that it is strongly recommended to avoid using rolling tags in a production environment. ++info https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/ +{{- end }} +{{- end -}} + +{{/* +Return the proper Storage Class for master +*/}} +{{- define "redis.master.storageClass" -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 does not support it, so we need to implement this if-else logic. +*/}} +{{- if .Values.global -}} + {{- if .Values.global.storageClass -}} + {{- if (eq "-" .Values.global.storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" .Values.global.storageClass -}} + {{- end -}} + {{- else -}} + {{- if .Values.master.persistence.storageClass -}} + {{- if (eq "-" .Values.master.persistence.storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" .Values.master.persistence.storageClass -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- else -}} + {{- if .Values.master.persistence.storageClass -}} + {{- if (eq "-" .Values.master.persistence.storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" .Values.master.persistence.storageClass -}} + {{- end -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper Storage Class for slave +*/}} +{{- define "redis.slave.storageClass" -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 does not support it, so we need to implement this if-else logic. +*/}} +{{- if .Values.global -}} + {{- if .Values.global.storageClass -}} + {{- if (eq "-" .Values.global.storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" .Values.global.storageClass -}} + {{- end -}} + {{- else -}} + {{- if .Values.slave.persistence.storageClass -}} + {{- if (eq "-" .Values.slave.persistence.storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" .Values.slave.persistence.storageClass -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- else -}} + {{- if .Values.slave.persistence.storageClass -}} + {{- if (eq "-" .Values.slave.persistence.storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" .Values.slave.persistence.storageClass -}} + {{- end -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/task5/APP-helm/charts/redis/templates/configmap.yaml b/task5/APP-helm/charts/redis/templates/configmap.yaml new file mode 100755 index 0000000..d17ec26 --- /dev/null +++ b/task5/APP-helm/charts/redis/templates/configmap.yaml @@ -0,0 +1,52 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "redis.fullname" . }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: + redis.conf: |- +{{- if .Values.configmap }} + # User-supplied configuration: +{{ tpl .Values.configmap . | indent 4 }} +{{- end }} + master.conf: |- + dir {{ .Values.master.persistence.path }} +{{- if .Values.master.configmap }} + # User-supplied master configuration: +{{ tpl .Values.master.configmap . | indent 4 }} +{{- end }} +{{- if .Values.master.disableCommands }} +{{- range .Values.master.disableCommands }} + rename-command {{ . }} "" +{{- end }} +{{- end }} + replica.conf: |- + dir {{ .Values.slave.persistence.path }} + slave-read-only yes +{{- if .Values.slave.configmap }} + # User-supplied slave configuration: +{{ tpl .Values.slave.configmap . | indent 4 }} +{{- end }} +{{- if .Values.slave.disableCommands }} +{{- range .Values.slave.disableCommands }} + rename-command {{ . }} "" +{{- end }} +{{- end }} +{{- if .Values.sentinel.enabled }} + sentinel.conf: |- + dir "/tmp" + bind 0.0.0.0 + port {{ .Values.sentinel.port }} + sentinel monitor {{ .Values.sentinel.masterSet }} {{ template "redis.fullname" . }}-master-0.{{ template "redis.fullname" . }}-headless.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} {{ .Values.redisPort }} {{ .Values.sentinel.quorum }} + sentinel down-after-milliseconds {{ .Values.sentinel.masterSet }} {{ .Values.sentinel.downAfterMilliseconds }} + sentinel failover-timeout {{ .Values.sentinel.masterSet }} {{ .Values.sentinel.failoverTimeout }} + sentinel parallel-syncs {{ .Values.sentinel.masterSet }} {{ .Values.sentinel.parallelSyncs }} +{{- if .Values.sentinel.configmap }} + # User-supplied sentinel configuration: +{{ tpl .Values.sentinel.configmap . | indent 4 }} +{{- end }} +{{- end }} diff --git a/task5/APP-helm/charts/redis/templates/headless-svc.yaml b/task5/APP-helm/charts/redis/templates/headless-svc.yaml new file mode 100755 index 0000000..909cbce --- /dev/null +++ b/task5/APP-helm/charts/redis/templates/headless-svc.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "redis.fullname" . }}-headless + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + type: ClusterIP + clusterIP: None + ports: + - name: redis + port: {{ .Values.redisPort }} + targetPort: redis +{{- if .Values.sentinel.enabled }} + - name: redis-sentinel + port: {{ .Values.sentinel.port }} + targetPort: redis-sentinel +{{- end }} + selector: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} diff --git a/task5/APP-helm/charts/redis/templates/health-configmap.yaml b/task5/APP-helm/charts/redis/templates/health-configmap.yaml new file mode 100755 index 0000000..35c61b5 --- /dev/null +++ b/task5/APP-helm/charts/redis/templates/health-configmap.yaml @@ -0,0 +1,134 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "redis.fullname" . }}-health + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: + ping_readiness_local.sh: |- +{{- if .Values.usePasswordFile }} + password_aux=`cat ${REDIS_PASSWORD_FILE}` + export REDIS_PASSWORD=$password_aux +{{- end }} + response=$( + timeout -s 9 $1 \ + redis-cli \ +{{- if .Values.usePassword }} + -a $REDIS_PASSWORD --no-auth-warning \ +{{- end }} + -h localhost \ + -p $REDIS_PORT \ + ping + ) + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi + ping_liveness_local.sh: |- +{{- if .Values.usePasswordFile }} + password_aux=`cat ${REDIS_PASSWORD_FILE}` + export REDIS_PASSWORD=$password_aux +{{- end }} + response=$( + timeout -s 9 $1 \ + redis-cli \ +{{- if .Values.usePassword }} + -a $REDIS_PASSWORD --no-auth-warning \ +{{- end }} + -h localhost \ + -p $REDIS_PORT \ + ping + ) + if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading the dataset in memory" ]; then + echo "$response" + exit 1 + fi +{{- if .Values.sentinel.enabled }} + ping_sentinel.sh: |- +{{- if .Values.usePasswordFile }} + password_aux=`cat ${REDIS_PASSWORD_FILE}` + export REDIS_PASSWORD=$password_aux +{{- end }} + response=$( + timeout -s 9 $1 \ + redis-cli \ +{{- if .Values.usePassword }} + -a $REDIS_PASSWORD --no-auth-warning \ +{{- end }} + -h localhost \ + -p $REDIS_SENTINEL_PORT \ + ping + ) + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi + parse_sentinels.awk: |- + /ip/ {FOUND_IP=1} + /port/ {FOUND_PORT=1} + /runid/ {FOUND_RUNID=1} + !/ip|port|runid/ { + if (FOUND_IP==1) { + IP=$1; FOUND_IP=0; + } + else if (FOUND_PORT==1) { + PORT=$1; + FOUND_PORT=0; + } else if (FOUND_RUNID==1) { + printf "\nsentinel known-sentinel {{ .Values.sentinel.masterSet }} %s %s %s", IP, PORT, $0; FOUND_RUNID=0; + } + } +{{- end }} + ping_readiness_master.sh: |- +{{- if .Values.usePasswordFile }} + password_aux=`cat ${REDIS_MASTER_PASSWORD_FILE}` + export REDIS_MASTER_PASSWORD=$password_aux +{{- end }} + response=$( + timeout -s 9 $1 \ + redis-cli \ +{{- if .Values.usePassword }} + -a $REDIS_MASTER_PASSWORD --no-auth-warning \ +{{- end }} + -h $REDIS_MASTER_HOST \ + -p $REDIS_MASTER_PORT_NUMBER \ + ping + ) + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi + ping_liveness_master.sh: |- +{{- if .Values.usePasswordFile }} + password_aux=`cat ${REDIS_MASTER_PASSWORD_FILE}` + export REDIS_MASTER_PASSWORD=$password_aux +{{- end }} + response=$( + timeout -s 9 $1 \ + redis-cli \ +{{- if .Values.usePassword }} + -a $REDIS_MASTER_PASSWORD --no-auth-warning \ +{{- end }} + -h $REDIS_MASTER_HOST \ + -p $REDIS_MASTER_PORT_NUMBER \ + ping + ) + if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading the dataset in memory" ]; then + echo "$response" + exit 1 + fi + ping_readiness_local_and_master.sh: |- + script_dir="$(dirname "$0")" + exit_status=0 + "$script_dir/ping_readiness_local.sh" $1 || exit_status=$? + "$script_dir/ping_readiness_master.sh" $1 || exit_status=$? + exit $exit_status + ping_liveness_local_and_master.sh: |- + script_dir="$(dirname "$0")" + exit_status=0 + "$script_dir/ping_liveness_local.sh" $1 || exit_status=$? + "$script_dir/ping_liveness_master.sh" $1 || exit_status=$? + exit $exit_status diff --git a/task5/APP-helm/charts/redis/templates/metrics-prometheus.yaml b/task5/APP-helm/charts/redis/templates/metrics-prometheus.yaml new file mode 100755 index 0000000..3f33454 --- /dev/null +++ b/task5/APP-helm/charts/redis/templates/metrics-prometheus.yaml @@ -0,0 +1,30 @@ +{{- if and (.Values.metrics.enabled) (.Values.metrics.serviceMonitor.enabled) }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "redis.fullname" . }} + {{- if .Values.metrics.serviceMonitor.namespace }} + namespace: {{ .Values.metrics.serviceMonitor.namespace }} + {{- end }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + {{- range $key, $value := .Values.metrics.serviceMonitor.selector }} + {{ $key }}: {{ $value | quote }} + {{- end }} +spec: + endpoints: + - port: metrics + {{- if .Values.metrics.serviceMonitor.interval }} + interval: {{ .Values.metrics.serviceMonitor.interval }} + {{- end }} + selector: + matchLabels: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} +{{- end -}} diff --git a/task5/APP-helm/charts/redis/templates/metrics-svc.yaml b/task5/APP-helm/charts/redis/templates/metrics-svc.yaml new file mode 100755 index 0000000..ef39725 --- /dev/null +++ b/task5/APP-helm/charts/redis/templates/metrics-svc.yaml @@ -0,0 +1,30 @@ +{{- if .Values.metrics.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "redis.fullname" . }}-metrics + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + {{- if .Values.metrics.service.labels -}} + {{ toYaml .Values.metrics.service.labels | nindent 4 }} + {{- end -}} + {{- if .Values.metrics.service.annotations }} + annotations: {{ toYaml .Values.metrics.service.annotations | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.metrics.service.type }} + {{ if eq .Values.metrics.service.type "LoadBalancer" -}} {{ if .Values.metrics.service.loadBalancerIP -}} + loadBalancerIP: {{ .Values.metrics.service.loadBalancerIP }} + {{ end -}} + {{- end -}} + ports: + - name: metrics + port: 9121 + targetPort: metrics + selector: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} +{{- end }} diff --git a/task5/APP-helm/charts/redis/templates/networkpolicy.yaml b/task5/APP-helm/charts/redis/templates/networkpolicy.yaml new file mode 100755 index 0000000..8c6b14a --- /dev/null +++ b/task5/APP-helm/charts/redis/templates/networkpolicy.yaml @@ -0,0 +1,65 @@ +{{- if .Values.networkPolicy.enabled }} +kind: NetworkPolicy +apiVersion: {{ template "networkPolicy.apiVersion" . }} +metadata: + name: {{ template "redis.fullname" . }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + podSelector: + matchLabels: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + {{- if .Values.cluster.enabled }} + policyTypes: + - Ingress + - Egress + egress: + # Allow outbound connections to other cluster pods + - ports: + - port: {{ .Values.redisPort }} + {{- if .Values.sentinel.enabled }} + - port: {{ .Values.sentinel.port }} + {{- end }} + to: + - podSelector: + matchLabels: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + {{- end }} + ingress: + # Allow inbound connections + - ports: + - port: {{ .Values.redisPort }} + {{- if .Values.sentinel.enabled }} + - port: {{ .Values.sentinel.port }} + {{- end }} + {{- if not .Values.networkPolicy.allowExternal }} + from: + - podSelector: + matchLabels: + {{ template "redis.fullname" . }}-client: "true" + {{- if .Values.metrics.enabled }} + - podSelector: + matchLabels: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + role: metrics + {{- end }} + {{- if .Values.cluster.enabled }} + - podSelector: + matchLabels: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + role: slave + {{- end }} + {{- end }} + {{- if .Values.metrics.enabled }} + # Allow prometheus scrapes for metrics + - ports: + - port: 9121 + {{- end }} +{{- end }} diff --git a/task5/APP-helm/charts/redis/templates/redis-master-statefulset.yaml b/task5/APP-helm/charts/redis/templates/redis-master-statefulset.yaml new file mode 100755 index 0000000..2e67493 --- /dev/null +++ b/task5/APP-helm/charts/redis/templates/redis-master-statefulset.yaml @@ -0,0 +1,408 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ template "redis.fullname" . }}-master + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + selector: + matchLabels: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + role: master + serviceName: {{ template "redis.fullname" . }}-headless + template: + metadata: + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + role: master +{{- if .Values.master.podLabels }} +{{ toYaml .Values.master.podLabels | indent 8 }} +{{- end }} +{{- if and .Values.metrics.enabled .Values.metrics.podLabels }} +{{ toYaml .Values.metrics.podLabels | indent 8 }} +{{- end }} + annotations: + checksum/health: {{ include (print $.Template.BasePath "/health-configmap.yaml") . | sha256sum }} + checksum/configmap: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- if .Values.master.podAnnotations }} +{{ toYaml .Values.master.podAnnotations | indent 8 }} + {{- end }} + {{- if and .Values.metrics.enabled .Values.metrics.podAnnotations }} +{{ toYaml .Values.metrics.podAnnotations | indent 8 }} + {{- end }} + spec: +{{- include "redis.imagePullSecrets" . | indent 6 }} + {{- if .Values.securityContext.enabled }} + securityContext: + fsGroup: {{ .Values.securityContext.fsGroup }} + {{- if .Values.securityContext.sysctls }} + sysctls: +{{ toYaml .Values.securityContext.sysctls | indent 8 }} + {{- end }} + {{- end }} + serviceAccountName: "{{ template "redis.serviceAccountName" . }}" + {{- if .Values.master.priorityClassName }} + priorityClassName: "{{ .Values.master.priorityClassName }}" + {{- end }} + {{- with .Values.master.affinity }} + affinity: +{{ tpl (toYaml .) $ | indent 8 }} + {{- end }} + {{- if .Values.master.nodeSelector }} + nodeSelector: +{{ toYaml .Values.master.nodeSelector | indent 8 }} + {{- end }} + {{- if .Values.master.tolerations }} + tolerations: +{{ toYaml .Values.master.tolerations | indent 8 }} + {{- end }} + {{- if .Values.master.schedulerName }} + schedulerName: "{{ .Values.master.schedulerName }}" + {{- end }} + containers: + - name: {{ template "redis.fullname" . }} + image: "{{ template "redis.image" . }}" + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + {{- if .Values.securityContext.enabled }} + securityContext: + runAsUser: {{ .Values.securityContext.runAsUser }} + {{- end }} + command: + - /bin/bash + - -c + - | + {{- if (eq .Values.securityContext.runAsUser 0.0) }} + useradd redis + chown -R redis {{ .Values.master.persistence.path }} + {{- end }} + if [[ -n $REDIS_PASSWORD_FILE ]]; then + password_aux=`cat ${REDIS_PASSWORD_FILE}` + export REDIS_PASSWORD=$password_aux + fi + if [[ ! -f /opt/bitnami/redis/etc/master.conf ]];then + cp /opt/bitnami/redis/mounted-etc/master.conf /opt/bitnami/redis/etc/master.conf + fi + if [[ ! -f /opt/bitnami/redis/etc/redis.conf ]];then + cp /opt/bitnami/redis/mounted-etc/redis.conf /opt/bitnami/redis/etc/redis.conf + fi + ARGS=("--port" "${REDIS_PORT}") + {{- if .Values.usePassword }} + ARGS+=("--requirepass" "${REDIS_PASSWORD}") + ARGS+=("--masterauth" "${REDIS_PASSWORD}") + {{- else }} + ARGS+=("--protected-mode" "no") + {{- end }} + ARGS+=("--include" "/opt/bitnami/redis/etc/redis.conf") + ARGS+=("--include" "/opt/bitnami/redis/etc/master.conf") + {{- if .Values.master.extraFlags }} + {{- range .Values.master.extraFlags }} + ARGS+=({{ . | quote }}) + {{- end }} + {{- end }} + {{- if .Values.master.command }} + {{ .Values.master.command }} ${ARGS[@]} + {{- else }} + redis-server "${ARGS[@]}" + {{- end }} + env: + - name: REDIS_REPLICATION_MODE + value: master + {{- if .Values.usePassword }} + {{- if .Values.usePasswordFile }} + - name: REDIS_PASSWORD_FILE + value: "/opt/bitnami/redis/secrets/redis-password" + {{- else }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "redis.secretName" . }} + key: {{ template "redis.secretPasswordKey" . }} + {{- end }} + {{- else }} + - name: ALLOW_EMPTY_PASSWORD + value: "yes" + {{- end }} + - name: REDIS_PORT + value: {{ .Values.redisPort | quote }} + ports: + - name: redis + containerPort: {{ .Values.redisPort }} + {{- if .Values.master.livenessProbe.enabled }} + livenessProbe: + initialDelaySeconds: {{ .Values.master.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.master.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.master.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.master.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.master.livenessProbe.failureThreshold }} + exec: + command: + - sh + - -c + - /health/ping_liveness_local.sh {{ .Values.master.livenessProbe.timeoutSeconds }} + {{- end }} + {{- if .Values.master.readinessProbe.enabled}} + readinessProbe: + initialDelaySeconds: {{ .Values.master.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.master.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.master.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.master.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.master.readinessProbe.failureThreshold }} + exec: + command: + - sh + - -c + - /health/ping_readiness_local.sh {{ .Values.master.livenessProbe.timeoutSeconds }} + {{- end }} + resources: +{{ toYaml .Values.master.resources | indent 10 }} + volumeMounts: + - name: health + mountPath: /health + {{- if .Values.usePasswordFile }} + - name: redis-password + mountPath: /opt/bitnami/redis/secrets/ + {{- end }} + - name: redis-data + mountPath: {{ .Values.master.persistence.path }} + subPath: {{ .Values.master.persistence.subPath }} + - name: config + mountPath: /opt/bitnami/redis/mounted-etc + - name: redis-tmp-conf + mountPath: /opt/bitnami/redis/etc/ + {{- if and .Values.cluster.enabled .Values.sentinel.enabled }} + - name: sentinel + image: "{{ template "sentinel.image" . }}" + imagePullPolicy: {{ .Values.sentinel.image.pullPolicy | quote }} + {{- if .Values.securityContext.enabled }} + securityContext: + runAsUser: {{ .Values.securityContext.runAsUser }} + {{- end }} + command: + - /bin/bash + - -c + - | + if [[ -n $REDIS_PASSWORD_FILE ]]; then + password_aux=`cat ${REDIS_PASSWORD_FILE}` + export REDIS_PASSWORD=$password_aux + fi + if [[ ! -f /opt/bitnami/redis-sentinel/etc/sentinel.conf ]];then + cp /opt/bitnami/redis-sentinel/mounted-etc/sentinel.conf /opt/bitnami/redis-sentinel/etc/sentinel.conf + {{- if .Values.usePassword }} + printf "\nsentinel auth-pass {{ .Values.sentinel.masterSet }} $REDIS_PASSWORD" >> /opt/bitnami/redis-sentinel/etc/sentinel.conf + {{- end }} + fi + echo "Getting information about current running sentinels" + # Get information from existing sentinels + existing_sentinels=$(timeout -s 9 {{ .Values.sentinel.initialCheckTimeout }} redis-cli --raw -h {{ template "redis.fullname" . }} -a "$REDIS_PASSWORD" -p {{ .Values.sentinel.service.sentinelPort }} SENTINEL sentinels {{ .Values.sentinel.masterSet }}) + echo "$existing_sentinels" | awk -f /health/parse_sentinels.awk | tee -a /opt/bitnami/redis-sentinel/etc/sentinel.conf + + redis-server /opt/bitnami/redis-sentinel/etc/sentinel.conf --sentinel + env: + {{- if .Values.usePassword }} + {{- if .Values.usePasswordFile }} + - name: REDIS_PASSWORD_FILE + value: "/opt/bitnami/redis/secrets/redis-password" + {{- else }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "redis.secretName" . }} + key: {{ template "redis.secretPasswordKey" . }} + {{- end }} + {{- else }} + - name: ALLOW_EMPTY_PASSWORD + value: "yes" + {{- end }} + - name: REDIS_SENTINEL_PORT + value: {{ .Values.sentinel.port | quote }} + ports: + - name: redis-sentinel + containerPort: {{ .Values.sentinel.port }} + {{- if .Values.sentinel.livenessProbe.enabled }} + livenessProbe: + initialDelaySeconds: {{ .Values.sentinel.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.sentinel.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.sentinel.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.sentinel.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.sentinel.livenessProbe.failureThreshold }} + exec: + command: + - sh + - -c + - /health/ping_sentinel.sh {{ .Values.sentinel.livenessProbe.timeoutSeconds }} + {{- end }} + {{- if .Values.sentinel.readinessProbe.enabled}} + readinessProbe: + initialDelaySeconds: {{ .Values.sentinel.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.sentinel.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.sentinel.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.sentinel.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.sentinel.readinessProbe.failureThreshold }} + exec: + command: + - sh + - -c + - /health/ping_sentinel.sh {{ .Values.sentinel.livenessProbe.timeoutSeconds }} + {{- end }} + resources: +{{ toYaml .Values.sentinel.resources | indent 10 }} + volumeMounts: + - name: health + mountPath: /health + {{- if .Values.usePasswordFile }} + - name: redis-password + mountPath: /opt/bitnami/redis/secrets/ + {{- end }} + - name: redis-data + mountPath: {{ .Values.master.persistence.path }} + subPath: {{ .Values.master.persistence.subPath }} + - name: config + mountPath: /opt/bitnami/redis-sentinel/mounted-etc + - name: sentinel-tmp-conf + mountPath: /opt/bitnami/redis-sentinel/etc/ + {{- end }} +{{- if .Values.metrics.enabled }} + - name: metrics + image: {{ template "redis.metrics.image" . }} + imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} + command: + - /bin/bash + - -c + - | + if [[ -f '/secrets/redis-password' ]]; then + export REDIS_PASSWORD=$(cat /secrets/redis-password) + fi + redis_exporter + args: + {{- range $key, $value := .Values.metrics.extraArgs }} + - --{{ $key }}={{ $value }} + {{- end }} + env: + - name: REDIS_ALIAS + value: {{ template "redis.fullname" . }} + {{- if and .Values.usePassword (not .Values.usePasswordFile) }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "redis.secretName" . }} + key: {{ template "redis.secretPasswordKey" . }} + {{- end }} + volumeMounts: + {{- if .Values.usePasswordFile }} + - name: redis-password + mountPath: /secrets/ + {{- end }} + ports: + - name: metrics + containerPort: 9121 + resources: +{{ toYaml .Values.metrics.resources | indent 10 }} +{{- end }} + {{- $needsVolumePermissions := and .Values.volumePermissions.enabled (and ( and .Values.master.persistence.enabled (not .Values.persistence.existingClaim) ) .Values.securityContext.enabled) }} + {{- if or $needsVolumePermissions .Values.sysctlImage.enabled }} + initContainers: + {{- if $needsVolumePermissions }} + - name: volume-permissions + image: "{{ template "redis.volumePermissions.image" . }}" + imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} + command: ["/bin/chown", "-R", "{{ .Values.securityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }}", "{{ .Values.master.persistence.path }}"] + securityContext: + runAsUser: 0 + resources: +{{ toYaml .Values.volumePermissions.resources | indent 10 }} + volumeMounts: + - name: redis-data + mountPath: {{ .Values.master.persistence.path }} + subPath: {{ .Values.master.persistence.subPath }} + {{- end }} + {{- if .Values.sysctlImage.enabled }} + - name: init-sysctl + image: {{ template "redis.sysctl.image" . }} + imagePullPolicy: {{ default "" .Values.sysctlImage.pullPolicy | quote }} + resources: +{{ toYaml .Values.sysctlImage.resources | indent 10 }} + {{- if .Values.sysctlImage.mountHostSys }} + volumeMounts: + - name: host-sys + mountPath: /host-sys + {{- end }} + command: +{{ toYaml .Values.sysctlImage.command | indent 10 }} + securityContext: + privileged: true + runAsUser: 0 + {{- end }} + {{- end }} + volumes: + - name: health + configMap: + name: {{ template "redis.fullname" . }}-health + defaultMode: 0755 + {{- if .Values.usePasswordFile }} + - name: redis-password + secret: + secretName: {{ template "redis.secretName" . }} + items: + - key: {{ template "redis.secretPasswordKey" . }} + path: redis-password + {{- end }} + - name: config + configMap: + name: {{ template "redis.fullname" . }} + {{- if not .Values.master.persistence.enabled }} + - name: "redis-data" + emptyDir: {} + {{- else }} + {{- if .Values.persistence.existingClaim }} + - name: "redis-data" + persistentVolumeClaim: + claimName: {{ .Values.persistence.existingClaim }} + {{- end }} + {{- end }} + {{- if .Values.sysctlImage.mountHostSys }} + - name: host-sys + hostPath: + path: /sys + {{- end }} + - name: redis-tmp-conf + emptyDir: {} + {{- if and .Values.cluster.enabled .Values.sentinel.enabled }} + - name: sentinel-tmp-conf + emptyDir: {} + {{- end }} + {{- if and .Values.master.persistence.enabled (not .Values.persistence.existingClaim) }} + volumeClaimTemplates: + - metadata: + name: redis-data + labels: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + component: master + spec: + accessModes: + {{- range .Values.master.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.master.persistence.size | quote }} + {{ include "redis.master.storageClass" . }} + {{- end }} + updateStrategy: + type: {{ .Values.master.statefulset.updateStrategy }} + {{- if .Values.master.statefulset.rollingUpdatePartition }} + {{- if (eq "Recreate" .Values.master.statefulset.updateStrategy) }} + rollingUpdate: null + {{- else }} + rollingUpdate: + partition: {{ .Values.master.statefulset.rollingUpdatePartition }} + {{- end }} + {{- end }} diff --git a/task5/APP-helm/charts/redis/templates/redis-master-svc.yaml b/task5/APP-helm/charts/redis/templates/redis-master-svc.yaml new file mode 100755 index 0000000..7af8755 --- /dev/null +++ b/task5/APP-helm/charts/redis/templates/redis-master-svc.yaml @@ -0,0 +1,34 @@ +{{- if not .Values.sentinel.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "redis.fullname" . }}-master + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + {{- if .Values.master.service.labels -}} + {{ toYaml .Values.master.service.labels | nindent 4 }} + {{- end -}} +{{- if .Values.master.service.annotations }} + annotations: {{ toYaml .Values.master.service.annotations | nindent 4 }} +{{- end }} +spec: + type: {{ .Values.master.service.type }} + {{ if eq .Values.master.service.type "LoadBalancer" -}} {{ if .Values.master.service.loadBalancerIP -}} + loadBalancerIP: {{ .Values.master.service.loadBalancerIP }} + {{ end -}} + {{- end -}} + ports: + - name: redis + port: {{ .Values.master.service.port }} + targetPort: redis + {{- if .Values.master.service.nodePort }} + nodePort: {{ .Values.master.service.nodePort }} + {{- end }} + selector: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + role: master +{{- end }} diff --git a/task5/APP-helm/charts/redis/templates/redis-role.yaml b/task5/APP-helm/charts/redis/templates/redis-role.yaml new file mode 100755 index 0000000..2ae9759 --- /dev/null +++ b/task5/APP-helm/charts/redis/templates/redis-role.yaml @@ -0,0 +1,13 @@ +{{- if and .Values.rbac.create .Values.rbac.role.rules -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "redis.fullname" . }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +rules: +{{ toYaml .Values.rbac.role.rules }} +{{- end -}} diff --git a/task5/APP-helm/charts/redis/templates/redis-rolebinding.yaml b/task5/APP-helm/charts/redis/templates/redis-rolebinding.yaml new file mode 100755 index 0000000..c9c0439 --- /dev/null +++ b/task5/APP-helm/charts/redis/templates/redis-rolebinding.yaml @@ -0,0 +1,18 @@ +{{- if .Values.rbac.create -}} +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: RoleBinding +metadata: + name: {{ template "redis.fullname" . }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "redis.fullname" . }} +subjects: +- kind: ServiceAccount + name: {{ template "redis.serviceAccountName" . }} +{{- end -}} diff --git a/task5/APP-helm/charts/redis/templates/redis-serviceaccount.yaml b/task5/APP-helm/charts/redis/templates/redis-serviceaccount.yaml new file mode 100755 index 0000000..f027176 --- /dev/null +++ b/task5/APP-helm/charts/redis/templates/redis-serviceaccount.yaml @@ -0,0 +1,11 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "redis.serviceAccountName" . }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- end -}} diff --git a/task5/APP-helm/charts/redis/templates/redis-slave-statefulset.yaml b/task5/APP-helm/charts/redis/templates/redis-slave-statefulset.yaml new file mode 100755 index 0000000..557ae1f --- /dev/null +++ b/task5/APP-helm/charts/redis/templates/redis-slave-statefulset.yaml @@ -0,0 +1,426 @@ +{{- if .Values.cluster.enabled }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ template "redis.fullname" . }}-slave + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: +{{- if .Values.slave.updateStrategy }} + strategy: +{{ toYaml .Values.slave.updateStrategy | indent 4 }} +{{- end }} + replicas: {{ .Values.cluster.slaveCount }} + serviceName: {{ template "redis.fullname" . }}-headless + selector: + matchLabels: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + role: slave + template: + metadata: + labels: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + chart: {{ template "redis.chart" . }} + role: slave + {{- if .Values.slave.podLabels }} +{{ toYaml .Values.slave.podLabels | indent 8 }} + {{- end }} + {{- if and .Values.metrics.enabled .Values.metrics.podLabels }} +{{ toYaml .Values.metrics.podLabels | indent 8 }} + {{- end }} + annotations: + checksum/health: {{ include (print $.Template.BasePath "/health-configmap.yaml") . | sha256sum }} + checksum/configmap: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- if .Values.slave.podAnnotations }} +{{ toYaml .Values.slave.podAnnotations | indent 8 }} + {{- end }} + {{- if and .Values.metrics.enabled .Values.metrics.podAnnotations }} +{{ toYaml .Values.metrics.podAnnotations | indent 8 }} + {{- end }} + spec: +{{- include "redis.imagePullSecrets" . | indent 6 }} + {{- if .Values.securityContext.enabled }} + securityContext: + fsGroup: {{ .Values.securityContext.fsGroup }} + {{- if .Values.securityContext.sysctls }} + sysctls: +{{ toYaml .Values.securityContext.sysctls | indent 8 }} + {{- end }} + {{- end }} + serviceAccountName: "{{ template "redis.serviceAccountName" . }}" + {{- if .Values.slave.priorityClassName }} + priorityClassName: "{{ .Values.slave.priorityClassName }}" + {{- end }} + {{- if .Values.slave.nodeSelector }} + nodeSelector: +{{ toYaml .Values.slave.nodeSelector | indent 8 }} + {{- end }} + {{- if .Values.slave.tolerations }} + tolerations: +{{ toYaml .Values.slave.tolerations | indent 8 }} + {{- end }} + {{- if .Values.slave.schedulerName }} + schedulerName: "{{ .Values.slave.schedulerName }}" + {{- end }} + {{- with .Values.slave.affinity }} + affinity: +{{ tpl (toYaml .) $ | indent 8 }} + {{- end }} + containers: + - name: {{ template "redis.fullname" . }} + image: {{ template "redis.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + {{- if .Values.securityContext.enabled }} + securityContext: + runAsUser: {{ .Values.securityContext.runAsUser }} + {{- end }} + command: + - /bin/bash + - -c + - | + {{- if (eq .Values.securityContext.runAsUser 0.0) }} + useradd redis + chown -R redis {{ .Values.slave.persistence.path }} + {{- end }} + if [[ -n $REDIS_PASSWORD_FILE ]]; then + password_aux=`cat ${REDIS_PASSWORD_FILE}` + export REDIS_PASSWORD=$password_aux + fi + if [[ -n $REDIS_MASTER_PASSWORD_FILE ]]; then + password_aux=`cat ${REDIS_MASTER_PASSWORD_FILE}` + export REDIS_MASTER_PASSWORD=$password_aux + fi + if [[ ! -f /opt/bitnami/redis/etc/replica.conf ]];then + cp /opt/bitnami/redis/mounted-etc/replica.conf /opt/bitnami/redis/etc/replica.conf + fi + if [[ ! -f /opt/bitnami/redis/etc/redis.conf ]];then + cp /opt/bitnami/redis/mounted-etc/redis.conf /opt/bitnami/redis/etc/redis.conf + fi + ARGS=("--port" "${REDIS_PORT}") + ARGS+=("--slaveof" "${REDIS_MASTER_HOST}" "${REDIS_MASTER_PORT_NUMBER}") + {{- if .Values.usePassword }} + ARGS+=("--requirepass" "${REDIS_PASSWORD}") + ARGS+=("--masterauth" "${REDIS_MASTER_PASSWORD}") + {{- else }} + ARGS+=("--protected-mode" "no") + {{- end }} + ARGS+=("--include" "/opt/bitnami/redis/etc/redis.conf") + ARGS+=("--include" "/opt/bitnami/redis/etc/replica.conf") + {{- if .Values.slave.extraFlags }} + {{- range .Values.slave.extraFlags }} + ARGS+=({{ . | quote }}) + {{- end }} + {{- end }} + {{- if .Values.slave.command }} + {{ .Values.slave.command }} "${ARGS[@]}" + {{- else }} + redis-server "${ARGS[@]}" + {{- end }} + env: + - name: REDIS_REPLICATION_MODE + value: slave + - name: REDIS_MASTER_HOST + value: {{ template "redis.fullname" . }}-master-0.{{ template "redis.fullname" . }}-headless.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} + - name: REDIS_PORT + value: {{ .Values.redisPort | quote }} + - name: REDIS_MASTER_PORT_NUMBER + value: {{ .Values.redisPort | quote }} + {{- if .Values.usePassword }} + {{- if .Values.usePasswordFile }} + - name: REDIS_PASSWORD_FILE + value: "/opt/bitnami/redis/secrets/redis-password" + - name: REDIS_MASTER_PASSWORD_FILE + value: "/opt/bitnami/redis/secrets/redis-password" + {{- else }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "redis.secretName" . }} + key: {{ template "redis.secretPasswordKey" . }} + - name: REDIS_MASTER_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "redis.secretName" . }} + key: {{ template "redis.secretPasswordKey" . }} + {{- end }} + {{- else }} + - name: ALLOW_EMPTY_PASSWORD + value: "yes" + {{- end }} + ports: + - name: redis + containerPort: {{ .Values.redisPort }} + {{- if .Values.slave.livenessProbe.enabled }} + livenessProbe: + initialDelaySeconds: {{ .Values.slave.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.slave.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.slave.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.slave.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.slave.livenessProbe.failureThreshold}} + exec: + command: + - sh + - -c + {{- if .Values.sentinel.enabled }} + - /health/ping_liveness_local.sh {{ .Values.slave.livenessProbe.timeoutSeconds }} + {{- else }} + - /health/ping_liveness_local_and_master.sh {{ .Values.slave.livenessProbe.timeoutSeconds }} + {{- end }} + {{- end }} + + {{- if .Values.slave.readinessProbe.enabled }} + readinessProbe: + initialDelaySeconds: {{ .Values.slave.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.slave.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.slave.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.slave.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.slave.readinessProbe.failureThreshold }} + exec: + command: + - sh + - -c + {{- if .Values.sentinel.enabled }} + - /health/ping_readiness_local.sh {{ .Values.slave.livenessProbe.timeoutSeconds }} + {{- else }} + - /health/ping_readiness_local_and_master.sh {{ .Values.slave.livenessProbe.timeoutSeconds }} + {{- end }} + {{- end }} + resources: +{{ toYaml .Values.slave.resources | indent 10 }} + volumeMounts: + - name: health + mountPath: /health + {{- if .Values.usePasswordFile }} + - name: redis-password + mountPath: /opt/bitnami/redis/secrets/ + {{- end }} + - name: redis-data + mountPath: /data + - name: config + mountPath: /opt/bitnami/redis/mounted-etc + - name: redis-tmp-conf + mountPath: /opt/bitnami/redis/etc + {{- if and .Values.cluster.enabled .Values.sentinel.enabled }} + - name: sentinel + image: "{{ template "sentinel.image" . }}" + imagePullPolicy: {{ .Values.sentinel.image.pullPolicy | quote }} + {{- if .Values.securityContext.enabled }} + securityContext: + runAsUser: {{ .Values.securityContext.runAsUser }} + {{- end }} + command: + - /bin/bash + - -c + - | + if [[ -n $REDIS_PASSWORD_FILE ]]; then + password_aux=`cat ${REDIS_PASSWORD_FILE}` + export REDIS_PASSWORD=$password_aux + fi + if [[ ! -f /opt/bitnami/redis-sentinel/etc/sentinel.conf ]];then + cp /opt/bitnami/redis-sentinel/mounted-etc/sentinel.conf /opt/bitnami/redis-sentinel/etc/sentinel.conf + {{- if .Values.usePassword }} + printf "\nsentinel auth-pass {{ .Values.sentinel.masterSet }} $REDIS_PASSWORD" >> /opt/bitnami/redis-sentinel/etc/sentinel.conf + {{- end }} + fi + + redis-server /opt/bitnami/redis-sentinel/etc/sentinel.conf --sentinel + env: + {{- if .Values.usePassword }} + {{- if .Values.usePasswordFile }} + - name: REDIS_PASSWORD_FILE + value: "/opt/bitnami/redis/secrets/redis-password" + {{- else }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "redis.secretName" . }} + key: {{ template "redis.secretPasswordKey" . }} + {{- end }} + {{- else }} + - name: ALLOW_EMPTY_PASSWORD + value: "yes" + {{- end }} + - name: REDIS_SENTINEL_PORT + value: {{ .Values.sentinel.port | quote }} + ports: + - name: redis-sentinel + containerPort: {{ .Values.sentinel.port }} + {{- if .Values.sentinel.livenessProbe.enabled }} + livenessProbe: + initialDelaySeconds: {{ .Values.sentinel.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.sentinel.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.sentinel.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.sentinel.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.sentinel.livenessProbe.failureThreshold }} + exec: + command: + - sh + - -c + - /health/ping_sentinel.sh {{ .Values.sentinel.livenessProbe.timeoutSeconds }} + {{- end }} + {{- if .Values.sentinel.readinessProbe.enabled}} + readinessProbe: + initialDelaySeconds: {{ .Values.sentinel.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.sentinel.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.sentinel.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.sentinel.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.sentinel.readinessProbe.failureThreshold }} + exec: + command: + - sh + - -c + - /health/ping_sentinel.sh {{ .Values.sentinel.livenessProbe.timeoutSeconds }} + {{- end }} + resources: +{{ toYaml .Values.sentinel.resources | indent 10 }} + volumeMounts: + - name: health + mountPath: /health + {{- if .Values.usePasswordFile }} + - name: redis-password + mountPath: /opt/bitnami/redis/secrets/ + {{- end }} + - name: redis-data + mountPath: {{ .Values.master.persistence.path }} + subPath: {{ .Values.master.persistence.subPath }} + - name: config + mountPath: /opt/bitnami/redis-sentinel/mounted-etc + - name: sentinel-tmp-conf + mountPath: /opt/bitnami/redis-sentinel/etc + {{- end }} +{{- if .Values.metrics.enabled }} + - name: metrics + image: {{ template "redis.metrics.image" . }} + imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} + command: + - /bin/bash + - -c + - | + if [[ -f '/secrets/redis-password' ]]; then + export REDIS_PASSWORD=$(cat /secrets/redis-password) + fi + redis_exporter + args: + {{- range $key, $value := .Values.metrics.extraArgs }} + - --{{ $key }}={{ $value }} + {{- end }} + env: + - name: REDIS_ALIAS + value: {{ template "redis.fullname" . }} + {{- if and .Values.usePassword (not .Values.usePasswordFile) }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "redis.secretName" . }} + key: {{ template "redis.secretPasswordKey" . }} + {{- end }} + volumeMounts: + {{- if .Values.usePasswordFile }} + - name: redis-password + mountPath: /secrets/ + {{- end }} + ports: + - name: metrics + containerPort: 9121 + resources: +{{ toYaml .Values.metrics.resources | indent 10 }} +{{- end }} + {{- $needsVolumePermissions := and .Values.volumePermissions.enabled (and .Values.slave.persistence.enabled .Values.securityContext.enabled) }} + {{- if or $needsVolumePermissions .Values.sysctlImage.enabled }} + initContainers: + {{- if $needsVolumePermissions }} + - name: volume-permissions + image: "{{ template "redis.volumePermissions.image" . }}" + imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} + command: ["/bin/chown", "-R", "{{ .Values.securityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }}", "{{ .Values.slave.persistence.path }}"] + securityContext: + runAsUser: 0 + resources: +{{ toYaml .Values.volumePermissions.resources | indent 10 }} + volumeMounts: + - name: redis-data + mountPath: {{ .Values.slave.persistence.path }} + subPath: {{ .Values.slave.persistence.subPath }} + {{- end }} + {{- if .Values.sysctlImage.enabled }} + - name: init-sysctl + image: {{ template "redis.sysctl.image" . }} + imagePullPolicy: {{ default "" .Values.sysctlImage.pullPolicy | quote }} + resources: +{{ toYaml .Values.sysctlImage.resources | indent 10 }} + {{- if .Values.sysctlImage.mountHostSys }} + volumeMounts: + - name: host-sys + mountPath: /host-sys + {{- end }} + command: +{{ toYaml .Values.sysctlImage.command | indent 10 }} + securityContext: + privileged: true + runAsUser: 0 + {{- end }} + {{- end }} + volumes: + - name: health + configMap: + name: {{ template "redis.fullname" . }}-health + defaultMode: 0755 + {{- if .Values.usePasswordFile }} + - name: redis-password + secret: + secretName: {{ template "redis.secretName" . }} + items: + - key: {{ template "redis.secretPasswordKey" . }} + path: redis-password + {{- end }} + - name: config + configMap: + name: {{ template "redis.fullname" . }} + {{- if .Values.sysctlImage.mountHostSys }} + - name: host-sys + hostPath: + path: /sys + {{- end }} + - name: sentinel-tmp-conf + emptyDir: {} + - name: redis-tmp-conf + emptyDir: {} + {{- if not .Values.slave.persistence.enabled }} + - name: redis-data + emptyDir: {} + {{- else }} + volumeClaimTemplates: + - metadata: + name: redis-data + labels: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + component: slave + spec: + accessModes: + {{- range .Values.slave.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.slave.persistence.size | quote }} + {{ include "redis.slave.storageClass" . }} + {{- end }} + updateStrategy: + type: {{ .Values.slave.statefulset.updateStrategy }} + {{- if .Values.slave.statefulset.rollingUpdatePartition }} + {{- if (eq "Recreate" .Values.slave.statefulset.updateStrategy) }} + rollingUpdate: null + {{- else }} + rollingUpdate: + partition: {{ .Values.slave.statefulset.rollingUpdatePartition }} + {{- end }} + {{- end }} +{{- end }} diff --git a/task5/APP-helm/charts/redis/templates/redis-slave-svc.yaml b/task5/APP-helm/charts/redis/templates/redis-slave-svc.yaml new file mode 100755 index 0000000..e6db293 --- /dev/null +++ b/task5/APP-helm/charts/redis/templates/redis-slave-svc.yaml @@ -0,0 +1,35 @@ +{{- if and .Values.cluster.enabled (not .Values.sentinel.enabled) }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "redis.fullname" . }}-slave + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + {{- if .Values.slave.service.labels -}} + {{ toYaml .Values.slave.service.labels | nindent 4 }} + {{- end -}} +{{- if .Values.slave.service.annotations }} + annotations: +{{ toYaml .Values.slave.service.annotations | indent 4 }} +{{- end }} +spec: + type: {{ .Values.slave.service.type }} + {{ if eq .Values.slave.service.type "LoadBalancer" -}} {{ if .Values.slave.service.loadBalancerIP -}} + loadBalancerIP: {{ .Values.slave.service.loadBalancerIP }} + {{ end -}} + {{- end -}} + ports: + - name: redis + port: {{ .Values.slave.service.port }} + targetPort: redis + {{- if .Values.slave.service.nodePort }} + nodePort: {{ .Values.slave.service.nodePort }} + {{- end }} + selector: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + role: slave +{{- end }} diff --git a/task5/APP-helm/charts/redis/templates/redis-with-sentinel-svc.yaml b/task5/APP-helm/charts/redis/templates/redis-with-sentinel-svc.yaml new file mode 100755 index 0000000..984de21 --- /dev/null +++ b/task5/APP-helm/charts/redis/templates/redis-with-sentinel-svc.yaml @@ -0,0 +1,40 @@ +{{- if .Values.sentinel.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "redis.fullname" . }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + {{- if .Values.sentinel.service.labels }} + {{ toYaml .Values.sentinel.service.labels | nindent 4 }} + {{- end }} +{{- if .Values.sentinel.service.annotations }} + annotations: +{{ toYaml .Values.sentinel.service.annotations | indent 4 }} +{{- end }} +spec: + type: {{ .Values.sentinel.service.type }} + {{ if eq .Values.sentinel.service.type "LoadBalancer" -}} {{ if .Values.sentinel.service.loadBalancerIP -}} + loadBalancerIP: {{ .Values.sentinel.service.loadBalancerIP }} + {{ end -}} + {{- end -}} + ports: + - name: redis + port: {{ .Values.sentinel.service.redisPort }} + targetPort: redis + {{- if .Values.sentinel.service.redisNodePort }} + nodePort: {{ .Values.sentinel.service.redisNodePort }} + {{- end }} + - name: redis-sentinel + port: {{ .Values.sentinel.service.sentinelPort }} + targetPort: redis-sentinel + {{- if .Values.sentinel.service.sentinelNodePort }} + nodePort: {{ .Values.sentinel.service.sentinelNodePort }} + {{- end }} + selector: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} +{{- end }} diff --git a/task5/APP-helm/charts/redis/templates/secret.yaml b/task5/APP-helm/charts/redis/templates/secret.yaml new file mode 100755 index 0000000..36c9ebf --- /dev/null +++ b/task5/APP-helm/charts/redis/templates/secret.yaml @@ -0,0 +1,18 @@ +{{- if and .Values.usePassword (not .Values.existingSecret) -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "redis.fullname" . }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +type: Opaque +data: + {{- if .Values.password }} + redis-password: {{ .Values.password | b64enc | quote }} + {{- else }} + redis-password: {{ randAlphaNum 10 | b64enc | quote }} + {{- end }} +{{- end -}} diff --git a/task5/APP-helm/charts/redis/values-production.yaml b/task5/APP-helm/charts/redis/values-production.yaml new file mode 100755 index 0000000..29ee258 --- /dev/null +++ b/task5/APP-helm/charts/redis/values-production.yaml @@ -0,0 +1,559 @@ +## Global Docker image parameters +## Please, note that this will override the image parameters, including dependencies, configured to use the global value +## Current available global Docker image parameters: imageRegistry and imagePullSecrets +## +# global: +# imageRegistry: myRegistryName +# imagePullSecrets: +# - myRegistryKeySecretName +# storageClass: myStorageClass + +## Bitnami Redis image version +## ref: https://hub.docker.com/r/bitnami/redis/tags/ +## +image: + registry: docker.io + repository: bitnami/redis + ## Bitnami Redis image tag + ## ref: https://github.com/bitnami/bitnami-docker-redis#supported-tags-and-respective-dockerfile-links + ## + tag: 5.0.5-debian-9-r141 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + +## String to partially override redis.fullname template (will maintain the release name) +## +# nameOverride: + +## String to fully override redis.fullname template +## +# fullnameOverride: + +## Cluster settings +cluster: + enabled: true + slaveCount: 3 + +## Use redis sentinel in the redis pod. This will disable the master and slave services and +## create one redis service with ports to the sentinel and the redis instances +sentinel: + enabled: false + ## Bitnami Redis Sentintel image version + ## ref: https://hub.docker.com/r/bitnami/redis-sentinel/tags/ + ## + image: + registry: docker.io + repository: bitnami/redis-sentinel + ## Bitnami Redis image tag + ## ref: https://github.com/bitnami/bitnami-docker-redis-sentinel#supported-tags-and-respective-dockerfile-links + ## + tag: 5.0.5-debian-9-r134 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + masterSet: mymaster + initialCheckTimeout: 5 + quorum: 2 + downAfterMilliseconds: 60000 + failoverTimeout: 18000 + parallelSyncs: 1 + port: 26379 + ## Additional Redis configuration for the sentinel nodes + ## ref: https://redis.io/topics/config + ## + configmap: + ## Configure extra options for Redis Sentinel liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 5 + ## Redis Sentinel resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + ## Redis Sentinel Service properties + service: + ## Redis Sentinel Service type + type: ClusterIP + sentinelPort: 26379 + redisPort: 6379 + + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # sentinelNodePort: + # redisNodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + labels: {} + loadBalancerIP: + +## Specifies the Kubernetes Cluster's Domain Name. +## +clusterDomain: cluster.local + +networkPolicy: + ## Specifies whether a NetworkPolicy should be created + ## + enabled: true + + ## The Policy model to apply. When set to false, only pods with the correct + ## client label will have network access to the port Redis is listening + ## on. When true, Redis will accept connections from any source + ## (with the correct destination port). + ## + # allowExternal: true + +serviceAccount: + ## Specifies whether a ServiceAccount should be created + ## + create: false + ## The name of the ServiceAccount to use. + ## If not set and create is true, a name is generated using the fullname template + name: + +rbac: + ## Specifies whether RBAC resources should be created + ## + create: false + + role: + ## Rules to create. It follows the role specification + # rules: + # - apiGroups: + # - extensions + # resources: + # - podsecuritypolicies + # verbs: + # - use + # resourceNames: + # - gce.unprivileged + rules: [] + +## Redis pod Security Context +securityContext: + enabled: true + fsGroup: 1001 + runAsUser: 1001 + ## sysctl settings for master and slave pods + ## + ## Uncomment the setting below to increase the net.core.somaxconn value + ## + # sysctls: + # - name: net.core.somaxconn + # value: "10000" + +## Use password authentication +usePassword: true +## Redis password (both master and slave) +## Defaults to a random 10-character alphanumeric string if not set and usePassword is true +## ref: https://github.com/bitnami/bitnami-docker-redis#setting-the-server-password-on-first-run +## +password: +## Use existing secret (ignores previous password) +# existingSecret: +## Password key to be retrieved from Redis secret +## +# existingSecretPasswordKey: + +## Mount secrets as files instead of environment variables +usePasswordFile: false + +## Persist data to a persistent volume (Redis Master) +persistence: {} + ## A manually managed Persistent Volume and Claim + ## Requires persistence.enabled: true + ## If defined, PVC must be created manually before volume will be bound + # existingClaim: + +# Redis port +redisPort: 6379 + +## +## Redis Master parameters +## +master: + ## Redis command arguments + ## + ## Can be used to specify command line arguments, for example: + ## + command: "/run.sh" + ## Additional Redis configuration for the master nodes + ## ref: https://redis.io/topics/config + ## + configmap: + ## Redis additional command line flags + ## + ## Can be used to specify command line flags, for example: + ## + ## extraFlags: + ## - "--maxmemory-policy volatile-ttl" + ## - "--repl-backlog-size 1024mb" + extraFlags: [] + ## Comma-separated list of Redis commands to disable + ## + ## Can be used to disable Redis commands for security reasons. + ## Commands will be completely disabled by renaming each to an empty string. + ## ref: https://redis.io/topics/security#disabling-of-specific-commands + ## + disableCommands: + - FLUSHDB + - FLUSHALL + + ## Redis Master additional pod labels and annotations + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + podLabels: {} + podAnnotations: {} + + ## Redis Master resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + + ## Configure extra options for Redis Master liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 5 + + ## Redis Master Node selectors and tolerations for pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#taints-and-tolerations-beta-feature + ## + # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} + # tolerations: [] + ## Redis Master pod/node affinity/anti-affinity + ## + affinity: {} + + ## Redis Master Service properties + service: + ## Redis Master Service type + type: ClusterIP + port: 6379 + + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # nodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + labels: {} + loadBalancerIP: + + ## Enable persistence using Persistent Volume Claims + ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + persistence: + enabled: true + ## The path the volume will be mounted at, useful when using different + ## Redis images. + path: /data + ## The subdirectory of the volume to mount to, useful in dev environments + ## and one PV for multiple services. + subPath: "" + ## redis data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + accessModes: + - ReadWriteOnce + size: 8Gi + + ## Update strategy, can be set to RollingUpdate or onDelete by default. + ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets + statefulset: + updateStrategy: RollingUpdate + ## Partition update strategy + ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions + # rollingUpdatePartition: + + ## Redis Master pod priorityClassName + # priorityClassName: {} + +## +## Redis Slave properties +## Note: service.type is a mandatory parameter +## The rest of the parameters are either optional or, if undefined, will inherit those declared in Redis Master +## +slave: + ## Slave Service properties + service: + ## Redis Slave Service type + type: ClusterIP + ## Redis port + port: 6379 + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # nodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + labels: {} + loadBalancerIP: + + ## Redis slave port + port: 6379 + ## Can be used to specify command line arguments, for example: + ## + command: "/run.sh" + ## Additional Redis configuration for the slave nodes + ## ref: https://redis.io/topics/config + ## + configmap: + ## Redis extra flags + extraFlags: [] + ## List of Redis commands to disable + disableCommands: + - FLUSHDB + - FLUSHALL + + ## Redis Slave pod/node affinity/anti-affinity + ## + affinity: {} + + ## Configure extra options for Redis Slave liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 10 + successThreshold: 1 + failureThreshold: 5 + + ## Redis slave Resource + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + + ## Redis slave selectors and tolerations for pod assignment + # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} + # tolerations: [] + + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + + ## Redis slave pod Annotation and Labels + podLabels: {} + podAnnotations: {} + + ## Redis slave pod priorityClassName + # priorityClassName: {} + + ## Enable persistence using Persistent Volume Claims + ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + persistence: + enabled: true + ## The path the volume will be mounted at, useful when using different + ## Redis images. + path: /data + ## The subdirectory of the volume to mount to, useful in dev environments + ## and one PV for multiple services. + subPath: "" + ## redis data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + accessModes: + - ReadWriteOnce + size: 8Gi + + ## Update strategy, can be set to RollingUpdate or onDelete by default. + ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets + statefulset: + updateStrategy: RollingUpdate + ## Partition update strategy + ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions + # rollingUpdatePartition: + +## Prometheus Exporter / Metrics +## +metrics: + enabled: true + + image: + registry: docker.io + repository: bitnami/redis-exporter + tag: 1.1.1-debian-9-r13 + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + + ## Metrics exporter resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + # resources: {} + + ## Extra arguments for Metrics exporter, for example: + ## extraArgs: + ## check-keys: myKey,myOtherKey + # extraArgs: {} + + ## Metrics exporter pod priorityClassName + # priorityClassName: {} + service: + type: ClusterIP + ## Use serviceLoadBalancerIP to request a specific static IP, + ## otherwise leave blank + # loadBalancerIP: + annotations: {} + labels: {} + + ## Metrics exporter pod Annotation and Labels + podAnnotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9121" + # podLabels: {} + + # Enable this if you're using https://github.com/coreos/prometheus-operator + serviceMonitor: + enabled: false + ## Specify a namespace if needed + # namespace: monitoring + # fallback to the prometheus default unless specified + # interval: 10s + ## Defaults to what's used if you follow CoreOS [Prometheus Install Instructions](https://github.com/helm/charts/tree/master/stable/prometheus-operator#tldr) + ## [Prometheus Selector Label](https://github.com/helm/charts/tree/master/stable/prometheus-operator#prometheus-operator-1) + ## [Kube Prometheus Selector Label](https://github.com/helm/charts/tree/master/stable/prometheus-operator#exporters) + selector: + prometheus: kube-prometheus + +## +## Init containers parameters: +## volumePermissions: Change the owner of the persist volume mountpoint to RunAsUser:fsGroup +## +volumePermissions: + enabled: false + image: + registry: docker.io + repository: bitnami/minideb + tag: stretch + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + resources: {} + # resources: + # requests: + # memory: 128Mi + # cpu: 100m + +## Redis config file +## ref: https://redis.io/topics/config +## +configmap: |- + # Enable AOF https://redis.io/topics/persistence#append-only-file + appendonly yes + # Disable RDB persistence, AOF persistence already enabled. + save "" + +## Sysctl InitContainer +## used to perform sysctl operation to modify Kernel settings (needed sometimes to avoid warnings) +sysctlImage: + enabled: false + command: [] + registry: docker.io + repository: bitnami/minideb + tag: stretch + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + mountHostSys: false + resources: {} + # resources: + # requests: + # memory: 128Mi + # cpu: 100m diff --git a/task5/APP-helm/charts/redis/values.yaml b/task5/APP-helm/charts/redis/values.yaml new file mode 100755 index 0000000..16e1ac2 --- /dev/null +++ b/task5/APP-helm/charts/redis/values.yaml @@ -0,0 +1,558 @@ +## Global Docker image parameters +## Please, note that this will override the image parameters, including dependencies, configured to use the global value +## Current available global Docker image parameters: imageRegistry and imagePullSecrets +## +# global: +# imageRegistry: myRegistryName +# imagePullSecrets: +# - myRegistryKeySecretName +# storageClass: myStorageClass + +## Bitnami Redis image version +## ref: https://hub.docker.com/r/bitnami/redis/tags/ +## +image: + registry: docker.io + repository: bitnami/redis + ## Bitnami Redis image tag + ## ref: https://github.com/bitnami/bitnami-docker-redis#supported-tags-and-respective-dockerfile-links + ## + tag: 5.0.5-debian-9-r141 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + +## String to partially override redis.fullname template (will maintain the release name) +## +# nameOverride: + +## String to fully override redis.fullname template +## +# fullnameOverride: + +## Cluster settings +cluster: + enabled: true + slaveCount: 2 + +## Use redis sentinel in the redis pod. This will disable the master and slave services and +## create one redis service with ports to the sentinel and the redis instances +sentinel: + enabled: false + ## Bitnami Redis Sentintel image version + ## ref: https://hub.docker.com/r/bitnami/redis-sentinel/tags/ + ## + image: + registry: docker.io + repository: bitnami/redis-sentinel + ## Bitnami Redis image tag + ## ref: https://github.com/bitnami/bitnami-docker-redis-sentinel#supported-tags-and-respective-dockerfile-links + ## + tag: 5.0.5-debian-9-r134 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + masterSet: mymaster + initialCheckTimeout: 5 + quorum: 2 + downAfterMilliseconds: 60000 + failoverTimeout: 18000 + parallelSyncs: 1 + port: 26379 + ## Additional Redis configuration for the sentinel nodes + ## ref: https://redis.io/topics/config + ## + configmap: + ## Configure extra options for Redis Sentinel liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 5 + ## Redis Sentinel resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + ## Redis Sentinel Service properties + service: + ## Redis Sentinel Service type + type: ClusterIP + sentinelPort: 26379 + redisPort: 6379 + + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # sentinelNodePort: + # redisNodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + labels: {} + loadBalancerIP: + +## Specifies the Kubernetes Cluster's Domain Name. +## +clusterDomain: cluster.local + +networkPolicy: + ## Specifies whether a NetworkPolicy should be created + ## + enabled: false + + ## The Policy model to apply. When set to false, only pods with the correct + ## client label will have network access to the port Redis is listening + ## on. When true, Redis will accept connections from any source + ## (with the correct destination port). + ## + # allowExternal: true + +serviceAccount: + ## Specifies whether a ServiceAccount should be created + ## + create: false + ## The name of the ServiceAccount to use. + ## If not set and create is true, a name is generated using the fullname template + name: + +rbac: + ## Specifies whether RBAC resources should be created + ## + create: false + + role: + ## Rules to create. It follows the role specification + # rules: + # - apiGroups: + # - extensions + # resources: + # - podsecuritypolicies + # verbs: + # - use + # resourceNames: + # - gce.unprivileged + rules: [] + +## Redis pod Security Context +securityContext: + enabled: true + fsGroup: 1001 + runAsUser: 1001 + ## sysctl settings for master and slave pods + ## + ## Uncomment the setting below to increase the net.core.somaxconn value + ## + # sysctls: + # - name: net.core.somaxconn + # value: "10000" + +## Use password authentication +usePassword: true +## Redis password (both master and slave) +## Defaults to a random 10-character alphanumeric string if not set and usePassword is true +## ref: https://github.com/bitnami/bitnami-docker-redis#setting-the-server-password-on-first-run +## +password: +## Use existing secret (ignores previous password) +# existingSecret: +## Password key to be retrieved from Redis secret +## +# existingSecretPasswordKey: + +## Mount secrets as files instead of environment variables +usePasswordFile: false + +## Persist data to a persistent volume (Redis Master) +persistence: {} + ## A manually managed Persistent Volume and Claim + ## Requires persistence.enabled: true + ## If defined, PVC must be created manually before volume will be bound + # existingClaim: + +# Redis port +redisPort: 6379 + +## +## Redis Master parameters +## +master: + ## Redis command arguments + ## + ## Can be used to specify command line arguments, for example: + ## + command: "/run.sh" + ## Additional Redis configuration for the master nodes + ## ref: https://redis.io/topics/config + ## + configmap: + ## Redis additional command line flags + ## + ## Can be used to specify command line flags, for example: + ## + ## extraFlags: + ## - "--maxmemory-policy volatile-ttl" + ## - "--repl-backlog-size 1024mb" + extraFlags: [] + ## Comma-separated list of Redis commands to disable + ## + ## Can be used to disable Redis commands for security reasons. + ## Commands will be completely disabled by renaming each to an empty string. + ## ref: https://redis.io/topics/security#disabling-of-specific-commands + ## + disableCommands: + - FLUSHDB + - FLUSHALL + + ## Redis Master additional pod labels and annotations + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + podLabels: {} + podAnnotations: {} + + ## Redis Master resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + + ## Configure extra options for Redis Master liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 5 + + ## Redis Master Node selectors and tolerations for pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#taints-and-tolerations-beta-feature + ## + # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} + # tolerations: [] + ## Redis Master pod/node affinity/anti-affinity + ## + affinity: {} + + ## Redis Master Service properties + service: + ## Redis Master Service type + type: ClusterIP + port: 6379 + + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # nodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + labels: {} + loadBalancerIP: + + ## Enable persistence using Persistent Volume Claims + ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + persistence: + enabled: true + ## The path the volume will be mounted at, useful when using different + ## Redis images. + path: /var/log + ## The subdirectory of the volume to mount to, useful in dev environments + ## and one PV for multiple services. + subPath: "" + ## redis data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + accessModes: + - ReadWriteOnce + size: 8Gi + + ## Update strategy, can be set to RollingUpdate or onDelete by default. + ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets + statefulset: + updateStrategy: RollingUpdate + ## Partition update strategy + ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions + # rollingUpdatePartition: + + ## Redis Master pod priorityClassName + # priorityClassName: {} + +## +## Redis Slave properties +## Note: service.type is a mandatory parameter +## The rest of the parameters are either optional or, if undefined, will inherit those declared in Redis Master +## +slave: + ## Slave Service properties + service: + ## Redis Slave Service type + type: ClusterIP + ## Redis port + port: 6379 + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # nodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + labels: {} + loadBalancerIP: + + ## Redis slave port + port: 6379 + ## Can be used to specify command line arguments, for example: + ## + command: "/run.sh" + ## Additional Redis configuration for the slave nodes + ## ref: https://redis.io/topics/config + ## + configmap: + ## Redis extra flags + extraFlags: [] + ## List of Redis commands to disable + disableCommands: + - FLUSHDB + - FLUSHALL + + ## Redis Slave pod/node affinity/anti-affinity + ## + affinity: {} + + ## Configure extra options for Redis Slave liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 10 + successThreshold: 1 + failureThreshold: 5 + + ## Redis slave Resource + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + + ## Redis slave selectors and tolerations for pod assignment + # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} + # tolerations: [] + + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + + ## Redis slave pod Annotation and Labels + podLabels: {} + podAnnotations: {} + + ## Redis slave pod priorityClassName + # priorityClassName: {} + + ## Enable persistence using Persistent Volume Claims + ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + persistence: + enabled: true + ## The path the volume will be mounted at, useful when using different + ## Redis images. + path: /data + ## The subdirectory of the volume to mount to, useful in dev environments + ## and one PV for multiple services. + subPath: "" + ## redis data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + accessModes: + - ReadWriteOnce + size: 8Gi + + ## Update strategy, can be set to RollingUpdate or onDelete by default. + ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets + statefulset: + updateStrategy: RollingUpdate + ## Partition update strategy + ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions + # rollingUpdatePartition: + +## Prometheus Exporter / Metrics +## +metrics: + enabled: false + + image: + registry: docker.io + repository: bitnami/redis-exporter + tag: 1.1.1-debian-9-r13 + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + ## Metrics exporter resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + # resources: {} + + ## Extra arguments for Metrics exporter, for example: + ## extraArgs: + ## check-keys: myKey,myOtherKey + # extraArgs: {} + + ## Metrics exporter pod Annotation and Labels + podAnnotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9121" + # podLabels: {} + + # Enable this if you're using https://github.com/coreos/prometheus-operator + serviceMonitor: + enabled: false + ## Specify a namespace if needed + # namespace: monitoring + # fallback to the prometheus default unless specified + # interval: 10s + ## Defaults to what's used if you follow CoreOS [Prometheus Install Instructions](https://github.com/helm/charts/tree/master/stable/prometheus-operator#tldr) + ## [Prometheus Selector Label](https://github.com/helm/charts/tree/master/stable/prometheus-operator#prometheus-operator-1) + ## [Kube Prometheus Selector Label](https://github.com/helm/charts/tree/master/stable/prometheus-operator#exporters) + selector: + prometheus: kube-prometheus + + ## Metrics exporter pod priorityClassName + # priorityClassName: {} + service: + type: ClusterIP + ## Use serviceLoadBalancerIP to request a specific static IP, + ## otherwise leave blank + # loadBalancerIP: + annotations: {} + labels: {} + +## +## Init containers parameters: +## volumePermissions: Change the owner of the persist volume mountpoint to RunAsUser:fsGroup +## +volumePermissions: + enabled: false + image: + registry: docker.io + repository: bitnami/minideb + tag: stretch + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + resources: {} + # resources: + # requests: + # memory: 128Mi + # cpu: 100m + +## Redis config file +## ref: https://redis.io/topics/config +## +configmap: |- + # Enable AOF https://redis.io/topics/persistence#append-only-file + appendonly yes + # Disable RDB persistence, AOF persistence already enabled. + save "" + +## Sysctl InitContainer +## used to perform sysctl operation to modify Kernel settings (needed sometimes to avoid warnings) +sysctlImage: + enabled: false + command: [] + registry: docker.io + repository: bitnami/minideb + tag: stretch + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + mountHostSys: false + resources: {} + # resources: + # requests: + # memory: 128Mi + # cpu: 100m diff --git a/APP-helm/deploy.sh b/task5/APP-helm/deploy.sh similarity index 100% rename from APP-helm/deploy.sh rename to task5/APP-helm/deploy.sh diff --git a/APP-helm/requirements.yaml b/task5/APP-helm/requirements.yaml similarity index 100% rename from APP-helm/requirements.yaml rename to task5/APP-helm/requirements.yaml diff --git a/APP-helm/templates/configmap.yaml b/task5/APP-helm/templates/configmap.yaml similarity index 100% rename from APP-helm/templates/configmap.yaml rename to task5/APP-helm/templates/configmap.yaml diff --git a/APP-helm/templates/deployment.yaml b/task5/APP-helm/templates/deployment.yaml similarity index 100% rename from APP-helm/templates/deployment.yaml rename to task5/APP-helm/templates/deployment.yaml diff --git a/APP-helm/templates/service.yaml b/task5/APP-helm/templates/service.yaml similarity index 100% rename from APP-helm/templates/service.yaml rename to task5/APP-helm/templates/service.yaml diff --git a/APP-helm/values.yaml b/task5/APP-helm/values.yaml similarity index 100% rename from APP-helm/values.yaml rename to task5/APP-helm/values.yaml From cca01dddc062949942e2a44bf7343ae8e9d1fe90 Mon Sep 17 00:00:00 2001 From: Saif Al Nuaimi Date: Sun, 1 Mar 2020 01:34:06 +0200 Subject: [PATCH 06/18] feat: commit --- APP/docker-compose.yml | 29 +- APP/local.dockerfile | 1 - APP/pom.xml | 17 +- .../com/webapp/app/config/RedisConfig.java | 30 - .../app/controller/CounterController.java | 82 +++ .../webapp/app/controller/CounterServlet.java | 56 -- .../main/java/com/webapp/app/model/User.java | 7 +- .../app/repository/MongoRepositoryImpl.java | 12 + .../app/repository/RedisRepository.java | 18 - .../app/repository/RedisRepositoryImpl.java | 49 -- APP/src/main/resources/application.properties | 5 +- APP/src/main/resources/static/main.js | 0 APP/src/main/resources/templates/index.html | 0 .../com/webapp/app/AppApplicationTests.java | 13 - docs/steps.txt | 13 +- task1/APP/Dockerfile | 13 + task1/APP/docker-compose.yml | 26 + task1/APP/local.dockerfile | 16 + task1/APP/mvnw | 0 task1/APP/pom.xml | 17 +- .../com/webapp/app/config/RedisConfig.java | 30 - .../app/controller/CounterController.java | 82 +++ .../webapp/app/controller/CounterServlet.java | 55 -- .../main/java/com/webapp/app/model/User.java | 7 +- .../app/repository/MongoRepositoryImpl.java | 12 + .../app/repository/RedisRepository.java | 18 - .../app/repository/RedisRepositoryImpl.java | 49 -- .../src/main/resources/application.properties | 5 +- .../com/webapp/app/AppApplicationTests.java | 13 - task3/APP/Dockerfile | 13 + task3/APP/docker-compose.yml | 31 +- task3/APP/local.dockerfile | 16 + task3/APP/pom.xml | 17 +- .../com/webapp/app/config/RedisConfig.java | 30 - .../app/controller/CounterController.java | 82 +++ .../webapp/app/controller/CounterServlet.java | 56 -- .../main/java/com/webapp/app/model/User.java | 7 +- .../app/repository/MongoRepositoryImpl.java | 12 + .../app/repository/RedisRepository.java | 18 - .../app/repository/RedisRepositoryImpl.java | 49 -- .../src/main/resources/application.properties | 5 +- task3/APP/src/main/resources/static/main.js | 0 .../src/main/resources/templates/index.html | 0 .../com/webapp/app/AppApplicationTests.java | 13 - task5/APP-helm/app.properties | 8 +- task5/APP-helm/charts/mongodb-4.8.4.tgz | Bin 0 -> 12940 bytes task5/APP-helm/charts/redis/.helmignore | 3 - task5/APP-helm/charts/redis/Chart.yaml | 21 - task5/APP-helm/charts/redis/README.md | 463 --------------- .../charts/redis/ci/default-values.yaml | 1 - .../APP-helm/charts/redis/ci/dev-values.yaml | 9 - .../charts/redis/ci/extra-flags-values.yaml | 11 - .../redis/ci/production-sentinel-values.yaml | 521 ---------------- .../charts/redis/ci/production-values.yaml | 522 ---------------- .../charts/redis/ci/redis-lib-values.yaml | 13 - .../redis/ci/redisgraph-module-values.yaml | 10 - .../APP-helm/charts/redis/templates/NOTES.txt | 104 ---- .../charts/redis/templates/_helpers.tpl | 320 ---------- .../charts/redis/templates/configmap.yaml | 52 -- .../charts/redis/templates/headless-svc.yaml | 24 - .../redis/templates/health-configmap.yaml | 134 ----- .../redis/templates/metrics-prometheus.yaml | 30 - .../charts/redis/templates/metrics-svc.yaml | 30 - .../charts/redis/templates/networkpolicy.yaml | 65 -- .../templates/redis-master-statefulset.yaml | 408 ------------- .../redis/templates/redis-master-svc.yaml | 34 -- .../charts/redis/templates/redis-role.yaml | 13 - .../redis/templates/redis-rolebinding.yaml | 18 - .../redis/templates/redis-serviceaccount.yaml | 11 - .../templates/redis-slave-statefulset.yaml | 426 ------------- .../redis/templates/redis-slave-svc.yaml | 35 -- .../templates/redis-with-sentinel-svc.yaml | 40 -- .../charts/redis/templates/secret.yaml | 18 - .../charts/redis/values-production.yaml | 559 ------------------ task5/APP-helm/charts/redis/values.yaml | 558 ----------------- task5/APP-helm/deploy.sh | 8 +- task5/APP-helm/requirements.lock | 6 + task5/APP-helm/requirements.yaml | 5 +- task5/APP-helm/templates/configmap.yaml | 2 +- task5/APP-helm/templates/deployment.yaml | 19 +- task5/APP-helm/values.yaml | 38 +- 81 files changed, 506 insertions(+), 5057 deletions(-) delete mode 100644 APP/src/main/java/com/webapp/app/config/RedisConfig.java create mode 100644 APP/src/main/java/com/webapp/app/controller/CounterController.java delete mode 100644 APP/src/main/java/com/webapp/app/controller/CounterServlet.java create mode 100755 APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java delete mode 100644 APP/src/main/java/com/webapp/app/repository/RedisRepository.java delete mode 100644 APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java mode change 100644 => 100755 APP/src/main/resources/static/main.js mode change 100644 => 100755 APP/src/main/resources/templates/index.html delete mode 100644 APP/src/test/java/com/webapp/app/AppApplicationTests.java create mode 100644 task1/APP/Dockerfile create mode 100644 task1/APP/docker-compose.yml create mode 100644 task1/APP/local.dockerfile mode change 100755 => 100644 task1/APP/mvnw delete mode 100644 task1/APP/src/main/java/com/webapp/app/config/RedisConfig.java create mode 100644 task1/APP/src/main/java/com/webapp/app/controller/CounterController.java delete mode 100644 task1/APP/src/main/java/com/webapp/app/controller/CounterServlet.java create mode 100755 task1/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java delete mode 100755 task1/APP/src/main/java/com/webapp/app/repository/RedisRepository.java delete mode 100755 task1/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java delete mode 100644 task1/APP/src/test/java/com/webapp/app/AppApplicationTests.java create mode 100644 task3/APP/Dockerfile create mode 100644 task3/APP/local.dockerfile delete mode 100644 task3/APP/src/main/java/com/webapp/app/config/RedisConfig.java create mode 100644 task3/APP/src/main/java/com/webapp/app/controller/CounterController.java delete mode 100644 task3/APP/src/main/java/com/webapp/app/controller/CounterServlet.java create mode 100755 task3/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java delete mode 100644 task3/APP/src/main/java/com/webapp/app/repository/RedisRepository.java delete mode 100644 task3/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java mode change 100644 => 100755 task3/APP/src/main/resources/static/main.js mode change 100644 => 100755 task3/APP/src/main/resources/templates/index.html delete mode 100644 task3/APP/src/test/java/com/webapp/app/AppApplicationTests.java create mode 100644 task5/APP-helm/charts/mongodb-4.8.4.tgz delete mode 100755 task5/APP-helm/charts/redis/.helmignore delete mode 100755 task5/APP-helm/charts/redis/Chart.yaml delete mode 100755 task5/APP-helm/charts/redis/README.md delete mode 100755 task5/APP-helm/charts/redis/ci/default-values.yaml delete mode 100755 task5/APP-helm/charts/redis/ci/dev-values.yaml delete mode 100755 task5/APP-helm/charts/redis/ci/extra-flags-values.yaml delete mode 100755 task5/APP-helm/charts/redis/ci/production-sentinel-values.yaml delete mode 100755 task5/APP-helm/charts/redis/ci/production-values.yaml delete mode 100755 task5/APP-helm/charts/redis/ci/redis-lib-values.yaml delete mode 100755 task5/APP-helm/charts/redis/ci/redisgraph-module-values.yaml delete mode 100755 task5/APP-helm/charts/redis/templates/NOTES.txt delete mode 100755 task5/APP-helm/charts/redis/templates/_helpers.tpl delete mode 100755 task5/APP-helm/charts/redis/templates/configmap.yaml delete mode 100755 task5/APP-helm/charts/redis/templates/headless-svc.yaml delete mode 100755 task5/APP-helm/charts/redis/templates/health-configmap.yaml delete mode 100755 task5/APP-helm/charts/redis/templates/metrics-prometheus.yaml delete mode 100755 task5/APP-helm/charts/redis/templates/metrics-svc.yaml delete mode 100755 task5/APP-helm/charts/redis/templates/networkpolicy.yaml delete mode 100755 task5/APP-helm/charts/redis/templates/redis-master-statefulset.yaml delete mode 100755 task5/APP-helm/charts/redis/templates/redis-master-svc.yaml delete mode 100755 task5/APP-helm/charts/redis/templates/redis-role.yaml delete mode 100755 task5/APP-helm/charts/redis/templates/redis-rolebinding.yaml delete mode 100755 task5/APP-helm/charts/redis/templates/redis-serviceaccount.yaml delete mode 100755 task5/APP-helm/charts/redis/templates/redis-slave-statefulset.yaml delete mode 100755 task5/APP-helm/charts/redis/templates/redis-slave-svc.yaml delete mode 100755 task5/APP-helm/charts/redis/templates/redis-with-sentinel-svc.yaml delete mode 100755 task5/APP-helm/charts/redis/templates/secret.yaml delete mode 100755 task5/APP-helm/charts/redis/values-production.yaml delete mode 100755 task5/APP-helm/charts/redis/values.yaml mode change 100644 => 100755 task5/APP-helm/deploy.sh create mode 100644 task5/APP-helm/requirements.lock diff --git a/APP/docker-compose.yml b/APP/docker-compose.yml index c100b97..38a7ec1 100644 --- a/APP/docker-compose.yml +++ b/APP/docker-compose.yml @@ -1,15 +1,26 @@ version: '2' + services: - redis: - image: redis:3.0.3 - hostname: redis-server - ports: - - "6379:6379" - app1: + + #-----------------web app----------------- + webapp: build: context: . dockerfile: local.dockerfile - links: - - "redis:redis-server" + environment: + - SPRING_DATA_MONGODB_URI=mongodb://root:root@mongo-db:27017/ + ports: + - 8080:8080 + depends_on: + - mongo-db + + #-----------------mongo db----------------- + mongo-db: + restart: always + image: mongo ports: - - "8080:8080" \ No newline at end of file + - "27017:27017" + environment: + - MONGO_INITDB_ROOT_USERNAME=root + - MONGO_INITDB_ROOT_PASSWORD=root + - MONGO_INITDB_DATABASE=users \ No newline at end of file diff --git a/APP/local.dockerfile b/APP/local.dockerfile index 5b5c653..d8e2d56 100644 --- a/APP/local.dockerfile +++ b/APP/local.dockerfile @@ -11,7 +11,6 @@ WORKDIR /myrunningapp ARG hostname=saifnuaimii/app:v1.0.0 EXPOSE 8080 COPY --from=mvn_builder_stage1 /myapp . -RUN apk add redis CMD redis-cli -h redis-server -p 8080 CMD java -D -jar target/app-*.jar \ No newline at end of file diff --git a/APP/pom.xml b/APP/pom.xml index 9fafa3f..1029d36 100644 --- a/APP/pom.xml +++ b/APP/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.2.4.RELEASE + 2.0.4.RELEASE com.webapp @@ -23,24 +23,17 @@ org.springframework.boot spring-boot-starter-actuator + org.springframework.boot - spring-boot-starter-data-redis - - - io.lettuce - lettuce-core - - - - - redis.clients - jedis + spring-boot-starter-data-mongodb + org.springframework.boot spring-boot-starter-web + org.springframework.boot spring-boot-starter-test diff --git a/APP/src/main/java/com/webapp/app/config/RedisConfig.java b/APP/src/main/java/com/webapp/app/config/RedisConfig.java deleted file mode 100644 index 12621a5..0000000 --- a/APP/src/main/java/com/webapp/app/config/RedisConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.webapp.app.config; - - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.listener.ChannelTopic; -import org.springframework.data.redis.listener.RedisMessageListenerContainer; -import org.springframework.data.redis.serializer.GenericToStringSerializer; - -@Configuration -public class RedisConfig { - - @Bean - JedisConnectionFactory jedisConnectionFactory() { - JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(); - jedisConnectionFactory.getPoolConfig().setMaxIdle(30); - jedisConnectionFactory.getPoolConfig().setMinIdle(10); - return jedisConnectionFactory; - } - - @Bean - public RedisTemplate redisTemplate() { - final RedisTemplate template = new RedisTemplate(); - template.setConnectionFactory(jedisConnectionFactory()); - template.setValueSerializer(new GenericToStringSerializer(Object.class)); - return template; - } -} \ No newline at end of file diff --git a/APP/src/main/java/com/webapp/app/controller/CounterController.java b/APP/src/main/java/com/webapp/app/controller/CounterController.java new file mode 100644 index 0000000..8beebdf --- /dev/null +++ b/APP/src/main/java/com/webapp/app/controller/CounterController.java @@ -0,0 +1,82 @@ +package com.webapp.app.controller; + +import com.webapp.app.model.User; +import com.webapp.app.repository.MongoRepositoryImpl; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Optional; + + +@RestController +public class CounterController extends HttpServlet { + + private final MongoRepositoryImpl mongoRepository; + + private int iHitCounter; + + public CounterController(MongoRepositoryImpl mongoRepository) { + this.mongoRepository = mongoRepository; + iHitCounter = 0; + } + + @GetMapping("/") + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { + String userAgent = request.getHeader("User-Agent"); + + String remoteAddr = request.getHeader("X-FORWARDED-FOR"); + + if (StringUtils.isEmpty(remoteAddr)) { + remoteAddr = request.getRemoteAddr(); + } + + User user = getUpdatedUserInfo(remoteAddr); + + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + + printStartHTMLTag(out); + out.println("
"); + out.println("Hello World !"); + out.print("
"); + out.println("

Welcome to my website !

"); + out.print("
"); + out.println("You are visitor number: " + (++iHitCounter)); + out.print("
"); + out.println("IP number: " + remoteAddr); + out.print("
"); + out.println("User Agent: " + userAgent); + out.print("
"); + out.println("count : " + user.getCount()); + out.println("
"); + printEndHTMLTag(out); + } + + private User getUpdatedUserInfo(String remoteAddr) { + Optional current = mongoRepository.findById(remoteAddr); + User user = new User(remoteAddr, 1); + current.ifPresent(u -> user.setCount(u.getCount() + 1)); + mongoRepository.save(user); + return user; + } + + private void printEndHTMLTag(PrintWriter out) { + out.println(""); + out.println(""); + } + + private void printStartHTMLTag(PrintWriter out) { + out.println(""); + out.println(""); + out.println("Hello World"); + out.println(""); + out.println(""); + } + +} \ No newline at end of file diff --git a/APP/src/main/java/com/webapp/app/controller/CounterServlet.java b/APP/src/main/java/com/webapp/app/controller/CounterServlet.java deleted file mode 100644 index 0a67643..0000000 --- a/APP/src/main/java/com/webapp/app/controller/CounterServlet.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.webapp.app.controller; - -import com.webapp.app.model.User; -import com.webapp.app.repository.RedisRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Objects; - - -@RestController -public class CounterServlet extends HttpServlet { - @Autowired - private RedisRepository redisRepository; - private int iHitCounter; - - public void init() throws ServletException { - iHitCounter = 0; - } - - @GetMapping("/") - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - String remoteAddr = ""; - - if (request != null) { - remoteAddr = request.getHeader("X-FORWARDED-FOR"); - if (remoteAddr == null || "".equals(remoteAddr)) { - remoteAddr = request.getRemoteAddr(); - } - User current = redisRepository.findUser(remoteAddr); - User user = new User(remoteAddr, Objects.nonNull(current) ? current.getCount() + 1 : 1); - redisRepository.add(user); - - PrintWriter out = response.getWriter(); - out.println("Hello World !"); - out.println("

Welcome to my website !


"); - out.println("You are visitor number: " + (++iHitCounter)); - out.println("IP number: " + (remoteAddr)); - out.println("count : " + (user.getCount())); - } - - } - - - public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - doGet(request, response); - } -} \ No newline at end of file diff --git a/APP/src/main/java/com/webapp/app/model/User.java b/APP/src/main/java/com/webapp/app/model/User.java index 5829c9b..80ee670 100644 --- a/APP/src/main/java/com/webapp/app/model/User.java +++ b/APP/src/main/java/com/webapp/app/model/User.java @@ -1,11 +1,15 @@ package com.webapp.app.model; +import org.springframework.data.annotation.Id; + import java.io.Serializable; -public class User implements Serializable { +public class User implements Serializable { private static final long serialVersionUID = 1L; + + @Id private String id; private int count; @@ -14,7 +18,6 @@ public User(String id, int count) { this.count = count; } - public String getId() { return id; } diff --git a/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java b/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java new file mode 100755 index 0000000..3d49eef --- /dev/null +++ b/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java @@ -0,0 +1,12 @@ +package com.webapp.app.repository; + + +import com.webapp.app.model.User; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface MongoRepositoryImpl extends MongoRepository { + + +} diff --git a/APP/src/main/java/com/webapp/app/repository/RedisRepository.java b/APP/src/main/java/com/webapp/app/repository/RedisRepository.java deleted file mode 100644 index 3186cd3..0000000 --- a/APP/src/main/java/com/webapp/app/repository/RedisRepository.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.webapp.app.repository; - - -import com.webapp.app.model.User; - -import java.util.Map; - -public interface RedisRepository { - - Map findAllUsers(); - - void add(User user); - - void delete(String id); - - User findUser(String id); - -} diff --git a/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java b/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java deleted file mode 100644 index 02ea8a1..0000000 --- a/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.webapp.app.repository; - -import com.webapp.app.model.User; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.core.HashOperations; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Repository; - -import javax.annotation.PostConstruct; -import java.util.Map; - -@Repository -public class RedisRepositoryImpl implements RedisRepository { - private static final String KEY = "User"; - - private RedisTemplate redisTemplate; - private HashOperations hashOperations; - - @Autowired - public RedisRepositoryImpl(RedisTemplate redisTemplate) { - this.redisTemplate = redisTemplate; - } - - @PostConstruct - private void init() { - hashOperations = redisTemplate.opsForHash(); - } - - public void add(final User user) { - hashOperations.put(KEY, user.getId(), user); - } - - public void delete(final String id) { - hashOperations.delete(KEY, id); - } - - public User findUser(final String id) { - Object obj = hashOperations.get(KEY, id); - if (obj instanceof User) - return (User) obj; - return new User("", 0); - } - - public Map findAllUsers() { - return hashOperations.entries(KEY); - } - - -} diff --git a/APP/src/main/resources/application.properties b/APP/src/main/resources/application.properties index 7e42f9d..a01bd52 100644 --- a/APP/src/main/resources/application.properties +++ b/APP/src/main/resources/application.properties @@ -1,3 +1,2 @@ -spring.redis.host=redis-server -spring.redis.port=6379 -server.port=8080 \ No newline at end of file +spring.data.mongodb.uri=mongodb://root:root@localhost:27017/ +spring.data.mongodb.database=users \ No newline at end of file diff --git a/APP/src/main/resources/static/main.js b/APP/src/main/resources/static/main.js old mode 100644 new mode 100755 diff --git a/APP/src/main/resources/templates/index.html b/APP/src/main/resources/templates/index.html old mode 100644 new mode 100755 diff --git a/APP/src/test/java/com/webapp/app/AppApplicationTests.java b/APP/src/test/java/com/webapp/app/AppApplicationTests.java deleted file mode 100644 index 60eac84..0000000 --- a/APP/src/test/java/com/webapp/app/AppApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.webapp.app; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class AppApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/docs/steps.txt b/docs/steps.txt index 2257a97..36eecce 100644 --- a/docs/steps.txt +++ b/docs/steps.txt @@ -4,20 +4,17 @@ ________________ Task 1: -First of all I created a pom file dependency file that has all of the dependency i need (web app, redis, health checker) +First of all I created a pom file dependency file that has all of the dependency i need (web app, mongo database , health checker) at my CounterServlet.java controller file a add a annotations to till set my application as spring boot application. -I started with my hello world web app then how to get the count of the victors for my web app then get the ip address. +I started with my hello world web app then how to get the count of the visitors for my web app then get the ip address. -Now I start to configuration the redis database to get requites but i could not do that by my self honestly I asked i friend -to help my with rides implementation. :/ - -I could not implement rides database the right way but you can test it by run it locally. +Now I start to config the mongo database to get requests but i could not do that by my self honestly I asked a friend +to help my with mongo database implementation. :/ I save my app under /task1/APP and started with the extra: - the health check ready from the pom file dependency implementation. - -note: you need to save ip and user-agent to the data base and implement right way. + - user-agent ________________ Task 2: diff --git a/task1/APP/Dockerfile b/task1/APP/Dockerfile new file mode 100644 index 0000000..22a3e37 --- /dev/null +++ b/task1/APP/Dockerfile @@ -0,0 +1,13 @@ +FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 +EXPOSE 8080 +WORKDIR /myapp +COPY src /myapp/src +COPY pom.xml /myapp/pom.xml +RUN mvn clean install + +FROM openjdk:8-jre-alpine as mvn_runner_stage2 +WORKDIR /myrunningapp +ARG hostname=saifnuaimii/app:v1.0.0 +EXPOSE 8080 +COPY --from=mvn_builder_stage1 /myapp . +CMD java -D -jar target/app-*.jar \ No newline at end of file diff --git a/task1/APP/docker-compose.yml b/task1/APP/docker-compose.yml new file mode 100644 index 0000000..38a7ec1 --- /dev/null +++ b/task1/APP/docker-compose.yml @@ -0,0 +1,26 @@ +version: '2' + +services: + + #-----------------web app----------------- + webapp: + build: + context: . + dockerfile: local.dockerfile + environment: + - SPRING_DATA_MONGODB_URI=mongodb://root:root@mongo-db:27017/ + ports: + - 8080:8080 + depends_on: + - mongo-db + + #-----------------mongo db----------------- + mongo-db: + restart: always + image: mongo + ports: + - "27017:27017" + environment: + - MONGO_INITDB_ROOT_USERNAME=root + - MONGO_INITDB_ROOT_PASSWORD=root + - MONGO_INITDB_DATABASE=users \ No newline at end of file diff --git a/task1/APP/local.dockerfile b/task1/APP/local.dockerfile new file mode 100644 index 0000000..d8e2d56 --- /dev/null +++ b/task1/APP/local.dockerfile @@ -0,0 +1,16 @@ +FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 +EXPOSE 8080 +RUN apk add redis +WORKDIR /myapp +COPY src /myapp/src +COPY pom.xml /myapp/pom.xml +RUN mvn clean install + +FROM openjdk:8-jre-alpine as mvn_runner_stage2 +WORKDIR /myrunningapp +ARG hostname=saifnuaimii/app:v1.0.0 +EXPOSE 8080 +COPY --from=mvn_builder_stage1 /myapp . +CMD redis-cli -h redis-server -p 8080 + +CMD java -D -jar target/app-*.jar \ No newline at end of file diff --git a/task1/APP/mvnw b/task1/APP/mvnw old mode 100755 new mode 100644 diff --git a/task1/APP/pom.xml b/task1/APP/pom.xml index 9fafa3f..1029d36 100644 --- a/task1/APP/pom.xml +++ b/task1/APP/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.2.4.RELEASE + 2.0.4.RELEASE com.webapp @@ -23,24 +23,17 @@ org.springframework.boot spring-boot-starter-actuator + org.springframework.boot - spring-boot-starter-data-redis - - - io.lettuce - lettuce-core - - - - - redis.clients - jedis + spring-boot-starter-data-mongodb + org.springframework.boot spring-boot-starter-web + org.springframework.boot spring-boot-starter-test diff --git a/task1/APP/src/main/java/com/webapp/app/config/RedisConfig.java b/task1/APP/src/main/java/com/webapp/app/config/RedisConfig.java deleted file mode 100644 index 12621a5..0000000 --- a/task1/APP/src/main/java/com/webapp/app/config/RedisConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.webapp.app.config; - - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.listener.ChannelTopic; -import org.springframework.data.redis.listener.RedisMessageListenerContainer; -import org.springframework.data.redis.serializer.GenericToStringSerializer; - -@Configuration -public class RedisConfig { - - @Bean - JedisConnectionFactory jedisConnectionFactory() { - JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(); - jedisConnectionFactory.getPoolConfig().setMaxIdle(30); - jedisConnectionFactory.getPoolConfig().setMinIdle(10); - return jedisConnectionFactory; - } - - @Bean - public RedisTemplate redisTemplate() { - final RedisTemplate template = new RedisTemplate(); - template.setConnectionFactory(jedisConnectionFactory()); - template.setValueSerializer(new GenericToStringSerializer(Object.class)); - return template; - } -} \ No newline at end of file diff --git a/task1/APP/src/main/java/com/webapp/app/controller/CounterController.java b/task1/APP/src/main/java/com/webapp/app/controller/CounterController.java new file mode 100644 index 0000000..8beebdf --- /dev/null +++ b/task1/APP/src/main/java/com/webapp/app/controller/CounterController.java @@ -0,0 +1,82 @@ +package com.webapp.app.controller; + +import com.webapp.app.model.User; +import com.webapp.app.repository.MongoRepositoryImpl; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Optional; + + +@RestController +public class CounterController extends HttpServlet { + + private final MongoRepositoryImpl mongoRepository; + + private int iHitCounter; + + public CounterController(MongoRepositoryImpl mongoRepository) { + this.mongoRepository = mongoRepository; + iHitCounter = 0; + } + + @GetMapping("/") + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { + String userAgent = request.getHeader("User-Agent"); + + String remoteAddr = request.getHeader("X-FORWARDED-FOR"); + + if (StringUtils.isEmpty(remoteAddr)) { + remoteAddr = request.getRemoteAddr(); + } + + User user = getUpdatedUserInfo(remoteAddr); + + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + + printStartHTMLTag(out); + out.println("
"); + out.println("Hello World !"); + out.print("
"); + out.println("

Welcome to my website !

"); + out.print("
"); + out.println("You are visitor number: " + (++iHitCounter)); + out.print("
"); + out.println("IP number: " + remoteAddr); + out.print("
"); + out.println("User Agent: " + userAgent); + out.print("
"); + out.println("count : " + user.getCount()); + out.println("
"); + printEndHTMLTag(out); + } + + private User getUpdatedUserInfo(String remoteAddr) { + Optional current = mongoRepository.findById(remoteAddr); + User user = new User(remoteAddr, 1); + current.ifPresent(u -> user.setCount(u.getCount() + 1)); + mongoRepository.save(user); + return user; + } + + private void printEndHTMLTag(PrintWriter out) { + out.println(""); + out.println(""); + } + + private void printStartHTMLTag(PrintWriter out) { + out.println(""); + out.println(""); + out.println("Hello World"); + out.println(""); + out.println(""); + } + +} \ No newline at end of file diff --git a/task1/APP/src/main/java/com/webapp/app/controller/CounterServlet.java b/task1/APP/src/main/java/com/webapp/app/controller/CounterServlet.java deleted file mode 100644 index 0b3f47c..0000000 --- a/task1/APP/src/main/java/com/webapp/app/controller/CounterServlet.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.webapp.app.controller; - -import com.webapp.app.model.User; -import com.webapp.app.repository.RedisRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Objects; - - -@RestController -public class CounterServlet extends HttpServlet { - @Autowired - private RedisRepository redisRepository; - private int iHitCounter; - - public void init() throws ServletException { - iHitCounter = 0; - } - - @GetMapping("/") - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - String remoteAddr = ""; - - if (request != null) { - remoteAddr = request.getHeader("X-FORWARDED-FOR"); - if (remoteAddr == null || "".equals(remoteAddr)) { - remoteAddr = request.getRemoteAddr(); - } - User current = redisRepository.findUser(remoteAddr); - User user = new User(remoteAddr, Objects.nonNull(current) ? current.getCount() + 1 : 1); - redisRepository.add(user); - - PrintWriter out = response.getWriter(); - out.println("Hello World !"); - out.println("Welcome to my website !"); - out.println("IP number: " + (remoteAddr)); - out.println("count : " + (user.getCount())); - } - - } - - - public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - doGet(request, response); - } -} \ No newline at end of file diff --git a/task1/APP/src/main/java/com/webapp/app/model/User.java b/task1/APP/src/main/java/com/webapp/app/model/User.java index 5829c9b..80ee670 100644 --- a/task1/APP/src/main/java/com/webapp/app/model/User.java +++ b/task1/APP/src/main/java/com/webapp/app/model/User.java @@ -1,11 +1,15 @@ package com.webapp.app.model; +import org.springframework.data.annotation.Id; + import java.io.Serializable; -public class User implements Serializable { +public class User implements Serializable { private static final long serialVersionUID = 1L; + + @Id private String id; private int count; @@ -14,7 +18,6 @@ public User(String id, int count) { this.count = count; } - public String getId() { return id; } diff --git a/task1/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java b/task1/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java new file mode 100755 index 0000000..3d49eef --- /dev/null +++ b/task1/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java @@ -0,0 +1,12 @@ +package com.webapp.app.repository; + + +import com.webapp.app.model.User; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface MongoRepositoryImpl extends MongoRepository { + + +} diff --git a/task1/APP/src/main/java/com/webapp/app/repository/RedisRepository.java b/task1/APP/src/main/java/com/webapp/app/repository/RedisRepository.java deleted file mode 100755 index 3186cd3..0000000 --- a/task1/APP/src/main/java/com/webapp/app/repository/RedisRepository.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.webapp.app.repository; - - -import com.webapp.app.model.User; - -import java.util.Map; - -public interface RedisRepository { - - Map findAllUsers(); - - void add(User user); - - void delete(String id); - - User findUser(String id); - -} diff --git a/task1/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java b/task1/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java deleted file mode 100755 index 02ea8a1..0000000 --- a/task1/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.webapp.app.repository; - -import com.webapp.app.model.User; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.core.HashOperations; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Repository; - -import javax.annotation.PostConstruct; -import java.util.Map; - -@Repository -public class RedisRepositoryImpl implements RedisRepository { - private static final String KEY = "User"; - - private RedisTemplate redisTemplate; - private HashOperations hashOperations; - - @Autowired - public RedisRepositoryImpl(RedisTemplate redisTemplate) { - this.redisTemplate = redisTemplate; - } - - @PostConstruct - private void init() { - hashOperations = redisTemplate.opsForHash(); - } - - public void add(final User user) { - hashOperations.put(KEY, user.getId(), user); - } - - public void delete(final String id) { - hashOperations.delete(KEY, id); - } - - public User findUser(final String id) { - Object obj = hashOperations.get(KEY, id); - if (obj instanceof User) - return (User) obj; - return new User("", 0); - } - - public Map findAllUsers() { - return hashOperations.entries(KEY); - } - - -} diff --git a/task1/APP/src/main/resources/application.properties b/task1/APP/src/main/resources/application.properties index 7e42f9d..a01bd52 100644 --- a/task1/APP/src/main/resources/application.properties +++ b/task1/APP/src/main/resources/application.properties @@ -1,3 +1,2 @@ -spring.redis.host=redis-server -spring.redis.port=6379 -server.port=8080 \ No newline at end of file +spring.data.mongodb.uri=mongodb://root:root@localhost:27017/ +spring.data.mongodb.database=users \ No newline at end of file diff --git a/task1/APP/src/test/java/com/webapp/app/AppApplicationTests.java b/task1/APP/src/test/java/com/webapp/app/AppApplicationTests.java deleted file mode 100644 index 60eac84..0000000 --- a/task1/APP/src/test/java/com/webapp/app/AppApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.webapp.app; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class AppApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/task3/APP/Dockerfile b/task3/APP/Dockerfile new file mode 100644 index 0000000..22a3e37 --- /dev/null +++ b/task3/APP/Dockerfile @@ -0,0 +1,13 @@ +FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 +EXPOSE 8080 +WORKDIR /myapp +COPY src /myapp/src +COPY pom.xml /myapp/pom.xml +RUN mvn clean install + +FROM openjdk:8-jre-alpine as mvn_runner_stage2 +WORKDIR /myrunningapp +ARG hostname=saifnuaimii/app:v1.0.0 +EXPOSE 8080 +COPY --from=mvn_builder_stage1 /myapp . +CMD java -D -jar target/app-*.jar \ No newline at end of file diff --git a/task3/APP/docker-compose.yml b/task3/APP/docker-compose.yml index 4ebf5f7..38a7ec1 100644 --- a/task3/APP/docker-compose.yml +++ b/task3/APP/docker-compose.yml @@ -1,13 +1,26 @@ version: '2' + services: - redis: - image: redis:3.0.3 - hostname: redis-server + + #-----------------web app----------------- + webapp: + build: + context: . + dockerfile: local.dockerfile + environment: + - SPRING_DATA_MONGODB_URI=mongodb://root:root@mongo-db:27017/ ports: - - "6379:6379" - app1: - image: saifnuaimii/app:v1.0.0 - links: - - "redis:redis-server" + - 8080:8080 + depends_on: + - mongo-db + + #-----------------mongo db----------------- + mongo-db: + restart: always + image: mongo ports: - - "8080:8080" \ No newline at end of file + - "27017:27017" + environment: + - MONGO_INITDB_ROOT_USERNAME=root + - MONGO_INITDB_ROOT_PASSWORD=root + - MONGO_INITDB_DATABASE=users \ No newline at end of file diff --git a/task3/APP/local.dockerfile b/task3/APP/local.dockerfile new file mode 100644 index 0000000..d8e2d56 --- /dev/null +++ b/task3/APP/local.dockerfile @@ -0,0 +1,16 @@ +FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 +EXPOSE 8080 +RUN apk add redis +WORKDIR /myapp +COPY src /myapp/src +COPY pom.xml /myapp/pom.xml +RUN mvn clean install + +FROM openjdk:8-jre-alpine as mvn_runner_stage2 +WORKDIR /myrunningapp +ARG hostname=saifnuaimii/app:v1.0.0 +EXPOSE 8080 +COPY --from=mvn_builder_stage1 /myapp . +CMD redis-cli -h redis-server -p 8080 + +CMD java -D -jar target/app-*.jar \ No newline at end of file diff --git a/task3/APP/pom.xml b/task3/APP/pom.xml index 9fafa3f..1029d36 100644 --- a/task3/APP/pom.xml +++ b/task3/APP/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.2.4.RELEASE + 2.0.4.RELEASE com.webapp @@ -23,24 +23,17 @@ org.springframework.boot spring-boot-starter-actuator + org.springframework.boot - spring-boot-starter-data-redis - - - io.lettuce - lettuce-core - - - - - redis.clients - jedis + spring-boot-starter-data-mongodb + org.springframework.boot spring-boot-starter-web + org.springframework.boot spring-boot-starter-test diff --git a/task3/APP/src/main/java/com/webapp/app/config/RedisConfig.java b/task3/APP/src/main/java/com/webapp/app/config/RedisConfig.java deleted file mode 100644 index 12621a5..0000000 --- a/task3/APP/src/main/java/com/webapp/app/config/RedisConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.webapp.app.config; - - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.listener.ChannelTopic; -import org.springframework.data.redis.listener.RedisMessageListenerContainer; -import org.springframework.data.redis.serializer.GenericToStringSerializer; - -@Configuration -public class RedisConfig { - - @Bean - JedisConnectionFactory jedisConnectionFactory() { - JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(); - jedisConnectionFactory.getPoolConfig().setMaxIdle(30); - jedisConnectionFactory.getPoolConfig().setMinIdle(10); - return jedisConnectionFactory; - } - - @Bean - public RedisTemplate redisTemplate() { - final RedisTemplate template = new RedisTemplate(); - template.setConnectionFactory(jedisConnectionFactory()); - template.setValueSerializer(new GenericToStringSerializer(Object.class)); - return template; - } -} \ No newline at end of file diff --git a/task3/APP/src/main/java/com/webapp/app/controller/CounterController.java b/task3/APP/src/main/java/com/webapp/app/controller/CounterController.java new file mode 100644 index 0000000..8beebdf --- /dev/null +++ b/task3/APP/src/main/java/com/webapp/app/controller/CounterController.java @@ -0,0 +1,82 @@ +package com.webapp.app.controller; + +import com.webapp.app.model.User; +import com.webapp.app.repository.MongoRepositoryImpl; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Optional; + + +@RestController +public class CounterController extends HttpServlet { + + private final MongoRepositoryImpl mongoRepository; + + private int iHitCounter; + + public CounterController(MongoRepositoryImpl mongoRepository) { + this.mongoRepository = mongoRepository; + iHitCounter = 0; + } + + @GetMapping("/") + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { + String userAgent = request.getHeader("User-Agent"); + + String remoteAddr = request.getHeader("X-FORWARDED-FOR"); + + if (StringUtils.isEmpty(remoteAddr)) { + remoteAddr = request.getRemoteAddr(); + } + + User user = getUpdatedUserInfo(remoteAddr); + + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + + printStartHTMLTag(out); + out.println("
"); + out.println("Hello World !"); + out.print("
"); + out.println("

Welcome to my website !

"); + out.print("
"); + out.println("You are visitor number: " + (++iHitCounter)); + out.print("
"); + out.println("IP number: " + remoteAddr); + out.print("
"); + out.println("User Agent: " + userAgent); + out.print("
"); + out.println("count : " + user.getCount()); + out.println("
"); + printEndHTMLTag(out); + } + + private User getUpdatedUserInfo(String remoteAddr) { + Optional current = mongoRepository.findById(remoteAddr); + User user = new User(remoteAddr, 1); + current.ifPresent(u -> user.setCount(u.getCount() + 1)); + mongoRepository.save(user); + return user; + } + + private void printEndHTMLTag(PrintWriter out) { + out.println(""); + out.println(""); + } + + private void printStartHTMLTag(PrintWriter out) { + out.println(""); + out.println(""); + out.println("Hello World"); + out.println(""); + out.println(""); + } + +} \ No newline at end of file diff --git a/task3/APP/src/main/java/com/webapp/app/controller/CounterServlet.java b/task3/APP/src/main/java/com/webapp/app/controller/CounterServlet.java deleted file mode 100644 index 0a67643..0000000 --- a/task3/APP/src/main/java/com/webapp/app/controller/CounterServlet.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.webapp.app.controller; - -import com.webapp.app.model.User; -import com.webapp.app.repository.RedisRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Objects; - - -@RestController -public class CounterServlet extends HttpServlet { - @Autowired - private RedisRepository redisRepository; - private int iHitCounter; - - public void init() throws ServletException { - iHitCounter = 0; - } - - @GetMapping("/") - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - String remoteAddr = ""; - - if (request != null) { - remoteAddr = request.getHeader("X-FORWARDED-FOR"); - if (remoteAddr == null || "".equals(remoteAddr)) { - remoteAddr = request.getRemoteAddr(); - } - User current = redisRepository.findUser(remoteAddr); - User user = new User(remoteAddr, Objects.nonNull(current) ? current.getCount() + 1 : 1); - redisRepository.add(user); - - PrintWriter out = response.getWriter(); - out.println("Hello World !"); - out.println("

Welcome to my website !


"); - out.println("You are visitor number: " + (++iHitCounter)); - out.println("IP number: " + (remoteAddr)); - out.println("count : " + (user.getCount())); - } - - } - - - public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - doGet(request, response); - } -} \ No newline at end of file diff --git a/task3/APP/src/main/java/com/webapp/app/model/User.java b/task3/APP/src/main/java/com/webapp/app/model/User.java index 5829c9b..80ee670 100644 --- a/task3/APP/src/main/java/com/webapp/app/model/User.java +++ b/task3/APP/src/main/java/com/webapp/app/model/User.java @@ -1,11 +1,15 @@ package com.webapp.app.model; +import org.springframework.data.annotation.Id; + import java.io.Serializable; -public class User implements Serializable { +public class User implements Serializable { private static final long serialVersionUID = 1L; + + @Id private String id; private int count; @@ -14,7 +18,6 @@ public User(String id, int count) { this.count = count; } - public String getId() { return id; } diff --git a/task3/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java b/task3/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java new file mode 100755 index 0000000..3d49eef --- /dev/null +++ b/task3/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java @@ -0,0 +1,12 @@ +package com.webapp.app.repository; + + +import com.webapp.app.model.User; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface MongoRepositoryImpl extends MongoRepository { + + +} diff --git a/task3/APP/src/main/java/com/webapp/app/repository/RedisRepository.java b/task3/APP/src/main/java/com/webapp/app/repository/RedisRepository.java deleted file mode 100644 index 3186cd3..0000000 --- a/task3/APP/src/main/java/com/webapp/app/repository/RedisRepository.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.webapp.app.repository; - - -import com.webapp.app.model.User; - -import java.util.Map; - -public interface RedisRepository { - - Map findAllUsers(); - - void add(User user); - - void delete(String id); - - User findUser(String id); - -} diff --git a/task3/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java b/task3/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java deleted file mode 100644 index 02ea8a1..0000000 --- a/task3/APP/src/main/java/com/webapp/app/repository/RedisRepositoryImpl.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.webapp.app.repository; - -import com.webapp.app.model.User; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.core.HashOperations; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Repository; - -import javax.annotation.PostConstruct; -import java.util.Map; - -@Repository -public class RedisRepositoryImpl implements RedisRepository { - private static final String KEY = "User"; - - private RedisTemplate redisTemplate; - private HashOperations hashOperations; - - @Autowired - public RedisRepositoryImpl(RedisTemplate redisTemplate) { - this.redisTemplate = redisTemplate; - } - - @PostConstruct - private void init() { - hashOperations = redisTemplate.opsForHash(); - } - - public void add(final User user) { - hashOperations.put(KEY, user.getId(), user); - } - - public void delete(final String id) { - hashOperations.delete(KEY, id); - } - - public User findUser(final String id) { - Object obj = hashOperations.get(KEY, id); - if (obj instanceof User) - return (User) obj; - return new User("", 0); - } - - public Map findAllUsers() { - return hashOperations.entries(KEY); - } - - -} diff --git a/task3/APP/src/main/resources/application.properties b/task3/APP/src/main/resources/application.properties index 7e42f9d..a01bd52 100644 --- a/task3/APP/src/main/resources/application.properties +++ b/task3/APP/src/main/resources/application.properties @@ -1,3 +1,2 @@ -spring.redis.host=redis-server -spring.redis.port=6379 -server.port=8080 \ No newline at end of file +spring.data.mongodb.uri=mongodb://root:root@localhost:27017/ +spring.data.mongodb.database=users \ No newline at end of file diff --git a/task3/APP/src/main/resources/static/main.js b/task3/APP/src/main/resources/static/main.js old mode 100644 new mode 100755 diff --git a/task3/APP/src/main/resources/templates/index.html b/task3/APP/src/main/resources/templates/index.html old mode 100644 new mode 100755 diff --git a/task3/APP/src/test/java/com/webapp/app/AppApplicationTests.java b/task3/APP/src/test/java/com/webapp/app/AppApplicationTests.java deleted file mode 100644 index 60eac84..0000000 --- a/task3/APP/src/test/java/com/webapp/app/AppApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.webapp.app; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class AppApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/task5/APP-helm/app.properties b/task5/APP-helm/app.properties index 8dac551..67f95dd 100644 --- a/task5/APP-helm/app.properties +++ b/task5/APP-helm/app.properties @@ -1,3 +1,5 @@ -spring.redis.host: "redis-server" -spring.redis.port: "6379" -server.port: "8080" \ No newline at end of file +spring.data.mongodb.uri: "mongodb://root:root@my-app-usersmongodb:27017/?ssl=false&authSource=admin&authMechanism=SCRAM-SHA-1" +spring.data.mongodb.database: "users" +spring.data.mongodb.username: "root" +spring.data.mongodb.password: "root" + diff --git a/task5/APP-helm/charts/mongodb-4.8.4.tgz b/task5/APP-helm/charts/mongodb-4.8.4.tgz new file mode 100644 index 0000000000000000000000000000000000000000..4347387dd67bf852cdb25720cc3d01f5ed8c0bbd GIT binary patch literal 12940 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 **Tip**: List all releases using `helm list` - -## Uninstalling the Chart - -To uninstall/delete the `my-release` deployment: - -```bash -$ helm delete my-release -``` - -The command removes all the Kubernetes components associated with the chart and deletes the release. - -## Upgrading an existing Release to a new major version - -A major chart version change (like v1.2.3 -> v2.0.0) indicates that there is an -incompatible breaking change needing manual actions. - -### To 8.0.18 - -For releases with `metrics.enabled: true` the default tag for the exporter image is now `v1.x.x`. This introduces many changes including metrics names. You'll want to use [this dashboard](https://github.com/oliver006/redis_exporter/blob/master/contrib/grafana_prometheus_redis_dashboard.json) now. Please see the [redis_exporter github page](https://github.com/oliver006/redis_exporter#upgrading-from-0x-to-1x) for more details. - -### To 7.0.0 - -This version causes a change in the Redis Master StatefulSet definition, so the command helm upgrade would not work out of the box. As an alternative, one of the following could be done: - - - Recommended: Create a clone of the Redis Master PVC (for example, using projects like [this one](https://github.com/edseymour/pvc-transfer)). Then launch a fresh release reusing this cloned PVC. - - ``` - helm install stable/redis --set persistence.existingClaim= - ``` - - - Alternative (not recommended, do at your own risk): `helm delete --purge` does not remove the PVC assigned to the Redis Master StatefulSet. As a consequence, the following commands can be done to upgrade the release - - ``` - helm delete --purge - helm install stable/redis --name - ``` - -Previous versions of the chart were not using persistence in the slaves, so this upgrade would add it to them. Another important change is that no values are inherited from master to slaves. For example, in 6.0.0 `slaves.readinessProbe.periodSeconds`, if empty, would be set to `master.readinessProbe.periodSeconds`. This approach lacked transparency and was difficult to maintain. From now on, all the slave parameters must be configured just as it is done with the masters. - -Some values have changed as well: - - - `master.port` and `slave.port` have been changed to `redisPort` (same value for both master and slaves) - - `master.securityContext` and `slave.securityContext` have been changed to `securityContext`(same values for both master and slaves) - -By default, the upgrade will not change the cluster topology. In case you want to use Redis Sentinel, you must explicitly set `sentinel.enabled` to `true`. - -### To 6.0.0 - -Previous versions of the chart were using an init-container to change the permissions of the volumes. This was done in case the `securityContext` directive in the template was not enough for that (for example, with cephFS). In this new version of the chart, this container is disabled by default (which should not affect most of the deployments). If your installation still requires that init container, execute `helm upgrade` with the `--set volumePermissions.enabled=true`. - -### To 5.0.0 - -The default image in this release may be switched out for any image containing the `redis-server` -and `redis-cli` binaries. If `redis-server` is not the default image ENTRYPOINT, `master.command` -must be specified. - -#### Breaking changes -- `master.args` and `slave.args` are removed. Use `master.command` or `slave.command` instead in order to override the image entrypoint, or `master.extraFlags` to pass additional flags to `redis-server`. -- `disableCommands` is now interpreted as an array of strings instead of a string of comma separated values. -- `master.persistence.path` now defaults to `/data`. - -### 4.0.0 - -This version removes the `chart` label from the `spec.selector.matchLabels` -which is immutable since `StatefulSet apps/v1beta2`. It has been inadvertently -added, causing any subsequent upgrade to fail. See https://github.com/helm/charts/issues/7726. - -It also fixes https://github.com/helm/charts/issues/7726 where a deployment `extensions/v1beta1` can not be upgraded if `spec.selector` is not explicitly set. - -Finally, it fixes https://github.com/helm/charts/issues/7803 by removing mutable labels in `spec.VolumeClaimTemplate.metadata.labels` so that it is upgradable. - -In order to upgrade, delete the Redis StatefulSet before upgrading: -```bash -$ kubectl delete statefulsets.apps --cascade=false my-release-redis-master -``` -And edit the Redis slave (and metrics if enabled) deployment: -```bash -kubectl patch deployments my-release-redis-slave --type=json -p='[{"op": "remove", "path": "/spec/selector/matchLabels/chart"}]' -kubectl patch deployments my-release-redis-metrics --type=json -p='[{"op": "remove", "path": "/spec/selector/matchLabels/chart"}]' -``` - -## Configuration - -The following table lists the configurable parameters of the Redis chart and their default values. - -| Parameter | Description | Default | -|-----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| -| `global.imageRegistry` | Global Docker image registry | `nil` | -| `global.imagePullSecrets` | Global Docker registry secret names as an array | `[]` (does not add image pull secrets to deployed pods) | -| `global.storageClass` | Global storage class for dynamic provisioning | `nil` | -| `image.registry` | Redis Image registry | `docker.io` | -| `image.repository` | Redis Image name | `bitnami/redis` | -| `image.tag` | Redis Image tag | `{TAG_NAME}` | -| `image.pullPolicy` | Image pull policy | `IfNotPresent` | -| `image.pullSecrets` | Specify docker-registry secret names as an array | `nil` | -| `nameOverride` | String to partially override redis.fullname template with a string (will prepend the release name) | `nil` | -| `fullnameOverride` | String to fully override redis.fullname template with a string | `nil` | -| `cluster.enabled` | Use master-slave topology | `true` | -| `cluster.slaveCount` | Number of slaves | `1` | -| `existingSecret` | Name of existing secret object (for password authentication) | `nil` | -| `existingSecretPasswordKey` | Name of key containing password to be retrieved from the existing secret | `nil` | -| `usePassword` | Use password | `true` | -| `usePasswordFile` | Mount passwords as files instead of environment variables | `false` | -| `password` | Redis password (ignored if existingSecret set) | Randomly generated | -| `configmap` | Additional common Redis node configuration (this value is evaluated as a template) | See values.yaml | -| `clusterDomain` | Kubernetes DNS Domain name to use | `cluster.local` | -| `networkPolicy.enabled` | Enable NetworkPolicy | `false` | -| `networkPolicy.allowExternal` | Don't require client label for connections | `true` | -| `securityContext.enabled` | Enable security context (both redis master and slave pods) | `true` | -| `securityContext.fsGroup` | Group ID for the container (both redis master and slave pods) | `1001` | -| `securityContext.runAsUser` | User ID for the container (both redis master and slave pods) | `1001` | -| `securityContext.sysctls` | Set namespaced sysctls for the container (both redis master and slave pods) | `nil` | -| `serviceAccount.create` | Specifies whether a ServiceAccount should be created | `false` | -| `serviceAccount.name` | The name of the ServiceAccount to create | Generated using the fullname template | -| `rbac.create` | Specifies whether RBAC resources should be created | `false` | -| `rbac.role.rules` | Rules to create | `[]` | -| `metrics.enabled` | Start a side-car prometheus exporter | `false` | -| `metrics.image.registry` | Redis exporter image registry | `docker.io` | -| `metrics.image.repository` | Redis exporter image name | `bitnami/redis-exporter` | -| `metrics.image.tag` | Redis exporter image tag | `{TAG_NAME}` | -| `metrics.image.pullPolicy` | Image pull policy | `IfNotPresent` | -| `metrics.image.pullSecrets` | Specify docker-registry secret names as an array | `nil` | -| `metrics.extraArgs` | Extra arguments for the binary; possible values [here](https://github.com/oliver006/redis_exporter#flags) | {} | -| `metrics.podLabels` | Additional labels for Metrics exporter pod | {} | -| `metrics.podAnnotations` | Additional annotations for Metrics exporter pod | {} | -| `metrics.resources` | Exporter resource requests/limit | Memory: `256Mi`, CPU: `100m` | -| `metrics.serviceMonitor.enabled` | if `true`, creates a Prometheus Operator ServiceMonitor (also requires `metrics.enabled` to be `true`) | `false` | -| `metrics.serviceMonitor.namespace` | Optional namespace which Prometheus is running in | `nil` | -| `metrics.serviceMonitor.interval` | How frequently to scrape metrics (use by default, falling back to Prometheus' default) | `nil` | -| `metrics.serviceMonitor.selector` | Default to kube-prometheus install (CoreOS recommended), but should be set according to Prometheus install | `{ prometheus: kube-prometheus }` | -| `metrics.service.type` | Kubernetes Service type (redis metrics) | `ClusterIP` | -| `metrics.service.annotations` | Annotations for the services to monitor (redis master and redis slave service) | {} | -| `metrics.service.labels` | Additional labels for the metrics service | {} | -| `metrics.service.loadBalancerIP` | loadBalancerIP if redis metrics service type is `LoadBalancer` | `nil` | -| `metrics.priorityClassName` | Metrics exporter pod priorityClassName | {} | -| `persistence.existingClaim` | Provide an existing PersistentVolumeClaim | `nil` | -| `master.persistence.enabled` | Use a PVC to persist data (master node) | `true` | -| `master.persistence.path` | Path to mount the volume at, to use other images | `/data` | -| `master.persistence.subPath` | Subdirectory of the volume to mount at | `""` | -| `master.persistence.storageClass` | Storage class of backing PVC | `generic` | -| `master.persistence.accessModes` | Persistent Volume Access Modes | `[ReadWriteOnce]` | -| `master.persistence.size` | Size of data volume | `8Gi` | -| `master.statefulset.updateStrategy` | Update strategy for StatefulSet | onDelete | -| `master.statefulset.rollingUpdatePartition` | Partition update strategy | `nil` | -| `master.podLabels` | Additional labels for Redis master pod | {} | -| `master.podAnnotations` | Additional annotations for Redis master pod | {} | -| `redisPort` | Redis port (in both master and slaves) | `6379` | -| `master.command` | Redis master entrypoint string. The command `redis-server` is executed if this is not provided. | `/run.sh` | -| `master.configmap` | Additional Redis configuration for the master nodes (this value is evaluated as a template) | `nil` | -| `master.disableCommands` | Array of Redis commands to disable (master) | `["FLUSHDB", "FLUSHALL"]` | -| `master.extraFlags` | Redis master additional command line flags | [] | -| `master.nodeSelector` | Redis master Node labels for pod assignment | {"beta.kubernetes.io/arch": "amd64"} | -| `master.tolerations` | Toleration labels for Redis master pod assignment | [] | -| `master.affinity` | Affinity settings for Redis master pod assignment | {} | -| `master.schedulerName` | Name of an alternate scheduler | `nil` | -| `master.service.type` | Kubernetes Service type (redis master) | `ClusterIP` | -| `master.service.port` | Kubernetes Service port (redis master) | `6379` | -| `master.service.nodePort` | Kubernetes Service nodePort (redis master) | `nil` | -| `master.service.annotations` | annotations for redis master service | {} | -| `master.service.labels` | Additional labels for redis master service | {} | -| `master.service.loadBalancerIP` | loadBalancerIP if redis master service type is `LoadBalancer` | `nil` | -| `master.resources` | Redis master CPU/Memory resource requests/limits | Memory: `256Mi`, CPU: `100m` | -| `master.livenessProbe.enabled` | Turn on and off liveness probe (redis master pod) | `true` | -| `master.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated (redis master pod) | `30` | -| `master.livenessProbe.periodSeconds` | How often to perform the probe (redis master pod) | `30` | -| `master.livenessProbe.timeoutSeconds` | When the probe times out (redis master pod) | `5` | -| `master.livenessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis master pod) | `1` | -| `master.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | `5` | -| `master.readinessProbe.enabled` | Turn on and off readiness probe (redis master pod) | `true` | -| `master.readinessProbe.initialDelaySeconds` | Delay before readiness probe is initiated (redis master pod) | `5` | -| `master.readinessProbe.periodSeconds` | How often to perform the probe (redis master pod) | `10` | -| `master.readinessProbe.timeoutSeconds` | When the probe times out (redis master pod) | `1` | -| `master.readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis master pod) | `1` | -| `master.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | `5` | -| `master.priorityClassName` | Redis Master pod priorityClassName | {} | -| `volumePermissions.enabled` | Enable init container that changes volume permissions in the registry (for cases where the default k8s `runAsUser` and `fsUser` values do not work) | `false` | -| `volumePermissions.image.registry` | Init container volume-permissions image registry | `docker.io` | -| `volumePermissions.image.repository` | Init container volume-permissions image name | `bitnami/minideb` | -| `volumePermissions.image.tag` | Init container volume-permissions image tag | `stretch` | -| `volumePermissions.image.pullPolicy` | Init container volume-permissions image pull policy | `Always` | -| `volumePermissions.resources ` | Init container volume-permissions CPU/Memory resource requests/limits | {} | -| `slave.service.type` | Kubernetes Service type (redis slave) | `ClusterIP` | -| `slave.service.nodePort` | Kubernetes Service nodePort (redis slave) | `nil` | -| `slave.service.annotations` | annotations for redis slave service | {} | -| `slave.service.labels` | Additional labels for redis slave service | {} | -| `slave.service.port` | Kubernetes Service port (redis slave) | `6379` | -| `slave.service.loadBalancerIP` | LoadBalancerIP if Redis slave service type is `LoadBalancer` | `nil` | -| `slave.command` | Redis slave entrypoint array. The docker image's ENTRYPOINT is used if this is not provided. | `/run.sh` | -| `slave.configmap` | Additional Redis configuration for the slave nodes (this value is evaluated as a template) | `nil` | -| `slave.disableCommands` | Array of Redis commands to disable (slave) | `[FLUSHDB, FLUSHALL]` | -| `slave.extraFlags` | Redis slave additional command line flags | `[]` | -| `slave.livenessProbe.enabled` | Turn on and off liveness probe (redis slave pod) | `true` | -| `slave.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated (redis slave pod) | `30` | -| `slave.livenessProbe.periodSeconds` | How often to perform the probe (redis slave pod) | `10` | -| `slave.livenessProbe.timeoutSeconds` | When the probe times out (redis slave pod) | `5` | -| `slave.livenessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis slave pod) | `1` | -| `slave.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | `5` | -| `slave.readinessProbe.enabled` | Turn on and off slave.readiness probe (redis slave pod) | `true` | -| `slave.readinessProbe.initialDelaySeconds` | Delay before slave.readiness probe is initiated (redis slave pod) | `5` | -| `slave.readinessProbe.periodSeconds` | How often to perform the probe (redis slave pod) | `10` | -| `slave.readinessProbe.timeoutSeconds` | When the probe times out (redis slave pod) | `10` | -| `slave.readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis slave pod) | `1` | -| `slave.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. (redis slave pod) | `5` | -| `slave.persistence.enabled` | Use a PVC to persist data (slave node) | `true` | -| `slave.persistence.path` | Path to mount the volume at, to use other images | `/data` | -| `slave.persistence.subPath` | Subdirectory of the volume to mount at | `""` | -| `slave.persistence.storageClass` | Storage class of backing PVC | `generic` | -| `slave.persistence.accessModes` | Persistent Volume Access Modes | `[ReadWriteOnce]` | -| `slave.persistence.size` | Size of data volume | `8Gi` | -| `slave.statefulset.updateStrategy` | Update strategy for StatefulSet | onDelete | -| `slave.statefulset.rollingUpdatePartition` | Partition update strategy | `nil` | -| `slave.podLabels` | Additional labels for Redis slave pod | `master.podLabels` | -| `slave.podAnnotations` | Additional annotations for Redis slave pod | `master.podAnnotations` | -| `slave.schedulerName` | Name of an alternate scheduler | `nil` | -| `slave.resources` | Redis slave CPU/Memory resource requests/limits | `{}` | -| `slave.affinity` | Enable node/pod affinity for slaves | {} | -| `slave.priorityClassName` | Redis Slave pod priorityClassName | {} | -| `sentinel.enabled` | Enable sentinel containers | `false` | -| `sentinel.masterSet` | Name of the sentinel master set | `mymaster` | -| `sentinel.initialCheckTimeout` | Timeout for querying the redis sentinel service for the active sentinel list | `5` | -| `sentinel.quorum` | Quorum for electing a new master | `2` | -| `sentinel.downAfterMilliseconds` | Timeout for detecting a Redis node is down | `60000` | -| `sentinel.failoverTimeout` | Timeout for performing a election failover | `18000` | -| `sentinel.parallelSyncs` | Number of parallel syncs in the cluster | `1` | -| `sentinel.port` | Redis Sentinel port | `26379` | -| `sentinel.configmap` | Additional Redis configuration for the sentinel nodes (this value is evaluated as a template) | `nil` | -| `sentinel.service.type` | Kubernetes Service type (redis sentinel) | `ClusterIP` | -| `sentinel.service.nodePort` | Kubernetes Service nodePort (redis sentinel) | `nil` | -| `sentinel.service.annotations` | annotations for redis sentinel service | {} | -| `sentinel.service.labels` | Additional labels for redis sentinel service | {} | -| `sentinel.service.redisPort` | Kubernetes Service port for Redis read only operations | `6379` | -| `sentinel.service.sentinelPort` | Kubernetes Service port for Redis sentinel | `26379` | -| `sentinel.service.redisNodePort` | Kubernetes Service node port for Redis read only operations | `` | -| `sentinel.service.sentinelNodePort` | Kubernetes Service node port for Redis sentinel | `` | -| `sentinel.service.loadBalancerIP` | LoadBalancerIP if Redis sentinel service type is `LoadBalancer` | `nil` | -| `sentinel.livenessProbe.enabled` | Turn on and off liveness probe (redis sentinel pod) | `true` | -| `sentinel.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated (redis sentinel pod) | `5` | -| `sentinel.livenessProbe.periodSeconds` | How often to perform the probe (redis sentinel container) | `5` | -| `sentinel.livenessProbe.timeoutSeconds` | When the probe times out (redis sentinel container) | `5` | -| `sentinel.livenessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis sentinel container) | `1` | -| `sentinel.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | `5` | -| `sentinel.readinessProbe.enabled` | Turn on and off sentinel.readiness probe (redis sentinel pod) | `true` | -| `sentinel.readinessProbe.initialDelaySeconds` | Delay before sentinel.readiness probe is initiated (redis sentinel pod) | `5` | -| `sentinel.readinessProbe.periodSeconds` | How often to perform the probe (redis sentinel pod) | `5` | -| `sentinel.readinessProbe.timeoutSeconds` | When the probe times out (redis sentinel container) | `1` | -| `sentinel.readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis sentinel container) | `1` | -| `sentinel.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. (redis sentinel container) | `5` | -| `sentinel.resources` | Redis sentinel CPU/Memory resource requests/limits | `{}` | -| `sentinel.image.registry` | Redis Sentinel Image registry | `docker.io` | -| `sentinel.image.repository` | Redis Sentinel Image name | `bitnami/redis-sentinel` | -| `sentinel.image.tag` | Redis Sentinel Image tag | `{TAG_NAME}` | -| `sentinel.image.pullPolicy` | Image pull policy | `IfNotPresent` | -| `sentinel.image.pullSecrets` | Specify docker-registry secret names as an array | `nil` | -| `sysctlImage.enabled` | Enable an init container to modify Kernel settings | `false` | -| `sysctlImage.command` | sysctlImage command to execute | [] | -| `sysctlImage.registry` | sysctlImage Init container registry | `docker.io` | -| `sysctlImage.repository` | sysctlImage Init container name | `bitnami/minideb` | -| `sysctlImage.tag` | sysctlImage Init container tag | `stretch` | -| `sysctlImage.pullPolicy` | sysctlImage Init container pull policy | `Always` | -| `sysctlImage.mountHostSys` | Mount the host `/sys` folder to `/host-sys` | `false` | -| `sysctlImage.resources` | sysctlImage Init container CPU/Memory resource requests/limits | {} | - -Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, - -```bash -$ helm install --name my-release \ - --set password=secretpassword \ - stable/redis -``` - -The above command sets the Redis server password to `secretpassword`. - -Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, - -```bash -$ helm install --name my-release -f values.yaml stable/redis -``` - -> **Tip**: You can use the default [values.yaml](values.yaml) - -> **Note for minikube users**: Current versions of minikube (v0.24.1 at the time of writing) provision `hostPath` persistent volumes that are only writable by root. Using chart defaults cause pod failure for the Redis pod as it attempts to write to the `/bitnami` directory. Consider installing Redis with `--set persistence.enabled=false`. See minikube issue [1990](https://github.com/kubernetes/minikube/issues/1990) for more information. - -### Using password file -To use a password file for Redis you need to create a secret containing the password: - -```bash -$ kubectl create secret generic redis-password-file --from-file=/tmp/redis-password -``` -> *NOTE*: It is important that the file with the password must be called `redis-password` - -And deploy the Helm Chart using the secret name: - -```bash -$ helm install stable/redis --set usePassword=true,usePasswordFile=true,existingSecret=redis-password-file,sentinels.enabled=true,metrics.enabled=true -``` - -### Production configuration - -This chart includes a `values-production.yaml` file where you can find some parameters oriented to production configuration in comparison to the regular `values.yaml`. - -```console -$ helm install --name my-release -f ./values-production.yaml stable/redis -``` - -- Number of slaves: -```diff -- cluster.slaveCount: 2 -+ cluster.slaveCount: 3 -``` - -- Enable NetworkPolicy: -```diff -- networkPolicy.enabled: false -+ networkPolicy.enabled: true -``` - -- Start a side-car prometheus exporter: -```diff -- metrics.enabled: false -+ metrics.enabled: true -``` - -### [Rolling VS Immutable tags](https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/) - -It is strongly recommended to use immutable tags in a production environment. This ensures your deployment does not change automatically if the same tag is updated with a different image. - -Bitnami will release a new chart updating its containers if a new version of the main container, significant changes, or critical vulnerabilities exist. - -## NetworkPolicy - -To enable network policy for Redis, install -[a networking plugin that implements the Kubernetes NetworkPolicy spec](https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy#before-you-begin), -and set `networkPolicy.enabled` to `true`. - -For Kubernetes v1.5 & v1.6, you must also turn on NetworkPolicy by setting -the DefaultDeny namespace annotation. Note: this will enforce policy for _all_ pods in the namespace: - - kubectl annotate namespace default "net.beta.kubernetes.io/network-policy={\"ingress\":{\"isolation\":\"DefaultDeny\"}}" - -With NetworkPolicy enabled, only pods with the generated client label will be -able to connect to Redis. This label will be displayed in the output -after a successful install. - -## Persistence - -By default, the chart mounts a [Persistent Volume](http://kubernetes.io/docs/user-guide/persistent-volumes/) at the `/data` path. The volume is created using dynamic volume provisioning. If a Persistent Volume Claim already exists, specify it during installation. - -### Existing PersistentVolumeClaim - -1. Create the PersistentVolume -2. Create the PersistentVolumeClaim -3. Install the chart - -```bash -$ helm install --set persistence.existingClaim=PVC_NAME stable/redis -``` - -## Metrics - -The chart optionally can start a metrics exporter for [prometheus](https://prometheus.io). The metrics endpoint (port 9121) is exposed in the service. Metrics can be scraped from within the cluster using something similar as the described in the [example Prometheus scrape configuration](https://github.com/prometheus/prometheus/blob/master/documentation/examples/prometheus-kubernetes.yml). If metrics are to be scraped from outside the cluster, the Kubernetes API proxy can be utilized to access the endpoint. - -## Host Kernel Settings -Redis may require some changes in the kernel of the host machine to work as expected, in particular increasing the `somaxconn` value and disabling transparent huge pages. -To do so, you can set up a privileged initContainer with the `sysctlImage` config values, for example: -``` -sysctlImage: - enabled: true - mountHostSys: true - command: - - /bin/sh - - -c - - |- - install_packages procps - sysctl -w net.core.somaxconn=10000 - echo never > /host-sys/kernel/mm/transparent_hugepage/enabled -``` - -Alternatively, for Kubernetes 1.12+ you can set `securityContext.sysctls` which will configure sysctls for master and slave pods. Example: - -```yaml -securityContext: - sysctls: - - name: net.core.somaxconn - value: "10000" -``` - -Note that this will not disable transparent huge tables. - -## Cluster topologies - -### Default: Master-Slave - -When installing the chart with `cluster.enabled=true`, it will deploy a Redis master StatefulSet (only one master node allowed) and a Redis slave StatefulSet. The slaves will be read-replicas of the master. Two services will be exposed: - - - Redis Master service: Points to the master, where read-write operations can be performed - - Redis Slave service: Points to the slaves, where only read operations are allowed. - -In case the master crashes, the slaves will wait until the master node is respawned again by the Kubernetes Controller Manager. - -### Master-Slave with Sentinel - -When installing the chart with `cluster.enabled=true` and `sentinel.enabled=true`, it will deploy a Redis master StatefulSet (only one master allowed) and a Redis slave StatefulSet. In this case, the pods will contain en extra container with Redis Sentinel. This container will form a cluster of Redis Sentinel nodes, which will promote a new master in case the actual one fails. In addition to this, only one service is exposed: - - - Redis service: Exposes port 6379 for Redis read-only operations and port 26379 for accesing Redis Sentinel. - -For read-only operations, access the service using port 6379. For write operations, it's necessary to access the Redis Sentinel cluster and query the current master using the command below (using redis-cli or similar: - -``` -SENTINEL get-master-addr-by-name -``` -This command will return the address of the current master, which can be accessed from inside the cluster. - -In case the current master crashes, the Sentinel containers will elect a new master node. - -## Notable changes - -### 9.0.0 -The metrics exporter has been changed from a separate deployment to a sidecar container, due to the latest changes in the Redis exporter code. Check the [official page](https://github.com/oliver006/redis_exporter/) for more information. The metrics container image was changed from oliver006/redis_exporter to bitnami/redis-exporter (Bitnami's maintained package of oliver006/redis_exporter). - -### 7.0.0 -In order to improve the performance in case of slave failure, we added persistence to the read-only slaves. That means that we moved from Deployment to StatefulSets. This should not affect upgrades from previous versions of the chart, as the deployments did not contain any persistence at all. - -This version also allows enabling Redis Sentinel containers inside of the Redis Pods (feature disabled by default). In case the master crashes, a new Redis node will be elected as master. In order to query the current master (no redis master service is exposed), you need to query first the Sentinel cluster. Find more information [in this section](#master-slave-with-sentinel). diff --git a/task5/APP-helm/charts/redis/ci/default-values.yaml b/task5/APP-helm/charts/redis/ci/default-values.yaml deleted file mode 100755 index fc2ba60..0000000 --- a/task5/APP-helm/charts/redis/ci/default-values.yaml +++ /dev/null @@ -1 +0,0 @@ -# Leave this file empty to ensure that CI runs builds against the default configuration in values.yaml. diff --git a/task5/APP-helm/charts/redis/ci/dev-values.yaml b/task5/APP-helm/charts/redis/ci/dev-values.yaml deleted file mode 100755 index be01913..0000000 --- a/task5/APP-helm/charts/redis/ci/dev-values.yaml +++ /dev/null @@ -1,9 +0,0 @@ -master: - persistence: - enabled: false - -cluster: - enabled: true - slaveCount: 1 - -usePassword: false diff --git a/task5/APP-helm/charts/redis/ci/extra-flags-values.yaml b/task5/APP-helm/charts/redis/ci/extra-flags-values.yaml deleted file mode 100755 index 71132f7..0000000 --- a/task5/APP-helm/charts/redis/ci/extra-flags-values.yaml +++ /dev/null @@ -1,11 +0,0 @@ -master: - extraFlags: - - --maxmemory-policy allkeys-lru - persistence: - enabled: false -slave: - extraFlags: - - --maxmemory-policy allkeys-lru - persistence: - enabled: false -usePassword: false diff --git a/task5/APP-helm/charts/redis/ci/production-sentinel-values.yaml b/task5/APP-helm/charts/redis/ci/production-sentinel-values.yaml deleted file mode 100755 index c230a56..0000000 --- a/task5/APP-helm/charts/redis/ci/production-sentinel-values.yaml +++ /dev/null @@ -1,521 +0,0 @@ -## Global Docker image parameters -## Please, note that this will override the image parameters, including dependencies, configured to use the global value -## Current available global Docker image parameters: imageRegistry and imagePullSecrets -## -# global: -# imageRegistry: myRegistryName -# imagePullSecrets: -# - myRegistryKeySecretName - -## Bitnami Redis image version -## ref: https://hub.docker.com/r/bitnami/redis/tags/ -## -image: - registry: docker.io - repository: bitnami/redis - ## Bitnami Redis image tag - ## ref: https://github.com/bitnami/bitnami-docker-redis#supported-tags-and-respective-dockerfile-links - ## - tag: 5.0.5-debian-9-r36 - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - -## Redis pod Security Context -securityContext: - enabled: true - fsGroup: 1001 - runAsUser: 1001 - -## Cluster settings -cluster: - enabled: true - slaveCount: 3 - -## Use redis sentinel in the redis pod. This will disable the master and slave services and -## create one redis service with ports to the sentinel and the redis instances -sentinel: - enabled: true - ## Bitnami Redis Sentintel image version - ## ref: https://hub.docker.com/r/bitnami/redis-sentinel/tags/ - ## - image: - registry: docker.io - repository: bitnami/redis-sentinel - ## Bitnami Redis image tag - ## ref: https://github.com/bitnami/bitnami-docker-redis-sentinel#supported-tags-and-respective-dockerfile-links - ## - tag: 5.0.5-debian-9-r37 - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - masterSet: mymaster - initialCheckTimeout: 5 - quorum: 2 - downAfterMilliseconds: 60000 - failoverTimeout: 18000 - parallelSyncs: 1 - port: 26379 - ## Configure extra options for Redis Sentinel liveness and readiness probes - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) - ## - livenessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 5 - readinessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 5 - ## Redis Sentinel resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - # resources: - # requests: - # memory: 256Mi - # cpu: 100m - ## Redis Sentinel Service properties - service: - ## Redis Sentinel Service type - type: ClusterIP - sentinelPort: 26379 - redisPort: 6379 - - ## Specify the nodePort value for the LoadBalancer and NodePort service types. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - ## - # sentinelNodePort: - # redisNodePort: - - ## Provide any additional annotations which may be required. This can be used to - ## set the LoadBalancer service type to internal only. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer - ## - annotations: {} - loadBalancerIP: - -networkPolicy: - ## Specifies whether a NetworkPolicy should be created - ## - enabled: true - - ## The Policy model to apply. When set to false, only pods with the correct - ## client label will have network access to the port Redis is listening - ## on. When true, Redis will accept connections from any source - ## (with the correct destination port). - ## - # allowExternal: true - -serviceAccount: - ## Specifies whether a ServiceAccount should be created - ## - create: false - ## The name of the ServiceAccount to use. - ## If not set and create is true, a name is generated using the fullname template - name: - -rbac: - ## Specifies whether RBAC resources should be created - ## - create: false - - role: - ## Rules to create. It follows the role specification - # rules: - # - apiGroups: - # - extensions - # resources: - # - podsecuritypolicies - # verbs: - # - use - # resourceNames: - # - gce.unprivileged - rules: [] - - -## Use password authentication -usePassword: true -## Redis password (both master and slave) -## Defaults to a random 10-character alphanumeric string if not set and usePassword is true -## ref: https://github.com/bitnami/bitnami-docker-redis#setting-the-server-password-on-first-run -## -password: -## Use existing secret (ignores previous password) -# existingSecret: -## Password key to be retrieved from Redis secret -## -# existingSecretPasswordKey: - -## Mount secrets as files instead of environment variables -usePasswordFile: false - -## Persist data to a persistent volume -persistence: {} - ## A manually managed Persistent Volume and Claim - ## Requires persistence.enabled: true - ## If defined, PVC must be created manually before volume will be bound - # existingClaim: - -# Redis port -redisPort: 6379 - -## -## Redis Master parameters -## -master: - ## Redis command arguments - ## - ## Can be used to specify command line arguments, for example: - ## - command: "/run.sh" - ## Redis additional command line flags - ## - ## Can be used to specify command line flags, for example: - ## - ## extraFlags: - ## - "--maxmemory-policy volatile-ttl" - ## - "--repl-backlog-size 1024mb" - extraFlags: [] - ## Comma-separated list of Redis commands to disable - ## - ## Can be used to disable Redis commands for security reasons. - ## Commands will be completely disabled by renaming each to an empty string. - ## ref: https://redis.io/topics/security#disabling-of-specific-commands - ## - disableCommands: - - FLUSHDB - - FLUSHALL - - ## Redis Master additional pod labels and annotations - ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ - podLabels: {} - podAnnotations: {} - - ## Redis Master resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - # resources: - # requests: - # memory: 256Mi - # cpu: 100m - ## Use an alternate scheduler, e.g. "stork". - ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ - ## - # schedulerName: - - ## Configure extra options for Redis Master liveness and readiness probes - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) - ## - livenessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 5 - readinessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 5 - - ## Redis Master Node selectors and tolerations for pod assignment - ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector - ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#taints-and-tolerations-beta-feature - ## - # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} - # tolerations: [] - ## Redis Master pod/node affinity/anti-affinity - ## - affinity: {} - - ## Redis Master Service properties - service: - ## Redis Master Service type - type: ClusterIP - port: 6379 - - ## Specify the nodePort value for the LoadBalancer and NodePort service types. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - ## - # nodePort: - - ## Provide any additional annotations which may be required. This can be used to - ## set the LoadBalancer service type to internal only. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer - ## - annotations: {} - loadBalancerIP: - - ## Enable persistence using Persistent Volume Claims - ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ - ## - persistence: - enabled: true - ## The path the volume will be mounted at, useful when using different - ## Redis images. - path: /data - ## The subdirectory of the volume to mount to, useful in dev environments - ## and one PV for multiple services. - subPath: "" - ## redis data Persistent Volume Storage Class - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "-" - accessModes: - - ReadWriteOnce - size: 8Gi - - ## Update strategy, can be set to RollingUpdate or onDelete by default. - ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets - statefulset: - updateStrategy: RollingUpdate - ## Partition update strategy - ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions - # rollingUpdatePartition: - - ## Redis Master pod priorityClassName - # priorityClassName: {} - - -## -## Redis Slave properties -## Note: service.type is a mandatory parameter -## The rest of the parameters are either optional or, if undefined, will inherit those declared in Redis Master -## -slave: - ## Slave Service properties - service: - ## Redis Slave Service type - type: ClusterIP - ## Redis port - port: 6379 - ## Specify the nodePort value for the LoadBalancer and NodePort service types. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - ## - # nodePort: - - ## Provide any additional annotations which may be required. This can be used to - ## set the LoadBalancer service type to internal only. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer - ## - annotations: {} - loadBalancerIP: - - ## Redis slave port - port: 6379 - - ## Can be used to specify command line arguments, for example: - ## - command: "/run.sh" - ## Redis extra flags - extraFlags: [] - ## List of Redis commands to disable - disableCommands: - - FLUSHDB - - FLUSHALL - - ## Redis Slave pod/node affinity/anti-affinity - ## - affinity: {} - - ## Configure extra options for Redis Slave liveness and readiness probes - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) - ## - livenessProbe: - enabled: true - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 5 - readinessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 10 - timeoutSeconds: 10 - successThreshold: 1 - failureThreshold: 5 - - ## Redis slave Resource - # resources: - # requests: - # memory: 256Mi - # cpu: 100m - - ## Enable persistence using Persistent Volume Claims - ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ - ## - persistence: - enabled: true - ## The path the volume will be mounted at, useful when using different - ## Redis images. - path: /data - ## The subdirectory of the volume to mount to, useful in dev environments - ## and one PV for multiple services. - subPath: "" - ## redis data Persistent Volume Storage Class - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "-" - accessModes: - - ReadWriteOnce - size: 8Gi - - ## Update strategy, can be set to RollingUpdate or onDelete by default. - ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets - statefulset: - updateStrategy: RollingUpdate - ## Partition update strategy - ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions - # rollingUpdatePartition: - - ## Redis slave selectors and tolerations for pod assignment - # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} - # tolerations: [] - - ## Use an alternate scheduler, e.g. "stork". - ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ - ## - # schedulerName: - - ## Redis slave pod Annotation and Labels - podLabels: {} - podAnnotations: {} - - ## Redis slave pod priorityClassName - # priorityClassName: {} - -## Prometheus Exporter / Metrics -## -metrics: - enabled: true - - image: - registry: docker.io - repository: bitnami/redis-exporter - tag: 1.0.3-debian-9-r0 - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - - ## Metrics exporter resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - ## - # resources: {} - ## Metrics exporter pod priorityClassName - # priorityClassName: {} - service: - type: ClusterIP - ## Use serviceLoadBalancerIP to request a specific static IP, - ## otherwise leave blank - # loadBalancerIP: - annotations: {} - - ## Extra arguments for Metrics exporter, for example: - ## extraArgs: - ## check-keys: myKey,myOtherKey - # extraArgs: {} - - ## Metrics exporter pod Annotation and Labels - podAnnotations: - prometheus.io/scrape: "true" - prometheus.io/port: "9121" - # podLabels: {} - - # Enable this if you're using https://github.com/coreos/prometheus-operator - serviceMonitor: - enabled: false - ## Specify a namespace if needed - # namespace: monitoring - # fallback to the prometheus default unless specified - # interval: 10s - ## Defaults to what's used if you follow CoreOS [Prometheus Install Instructions](https://github.com/helm/charts/tree/master/stable/prometheus-operator#tldr) - ## [Prometheus Selector Label](https://github.com/helm/charts/tree/master/stable/prometheus-operator#prometheus-operator-1) - ## [Kube Prometheus Selector Label](https://github.com/helm/charts/tree/master/stable/prometheus-operator#exporters) - selector: - prometheus: kube-prometheus -## -## Init containers parameters: -## volumePermissions: Change the owner of the persist volume mountpoint to RunAsUser:fsGroup -## -volumePermissions: - enabled: false - image: - registry: docker.io - repository: bitnami/minideb - tag: stretch - pullPolicy: Always - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - resources: {} - # resources: - # requests: - # memory: 128Mi - # cpu: 100m - -## Redis config file -## ref: https://redis.io/topics/config -## -configmap: |- - # maxmemory-policy volatile-lru - -## Sysctl InitContainer -## used to perform sysctl operation to modify Kernel settings (needed sometimes to avoid warnings) -sysctlImage: - enabled: false - command: [] - registry: docker.io - repository: bitnami/minideb - tag: stretch - pullPolicy: Always - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - mountHostSys: false - resources: {} - # resources: - # requests: - # memory: 128Mi - # cpu: 100m diff --git a/task5/APP-helm/charts/redis/ci/production-values.yaml b/task5/APP-helm/charts/redis/ci/production-values.yaml deleted file mode 100755 index 29f7153..0000000 --- a/task5/APP-helm/charts/redis/ci/production-values.yaml +++ /dev/null @@ -1,522 +0,0 @@ -## Global Docker image parameters -## Please, note that this will override the image parameters, including dependencies, configured to use the global value -## Current available global Docker image parameters: imageRegistry and imagePullSecrets -## -# global: -# imageRegistry: myRegistryName -# imagePullSecrets: -# - myRegistryKeySecretName - -## Bitnami Redis image version -## ref: https://hub.docker.com/r/bitnami/redis/tags/ -## -image: - registry: docker.io - repository: bitnami/redis - ## Bitnami Redis image tag - ## ref: https://github.com/bitnami/bitnami-docker-redis#supported-tags-and-respective-dockerfile-links - ## - tag: 5.0.5-debian-9-r36 - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - -## Redis pod Security Context -securityContext: - enabled: true - fsGroup: 1001 - runAsUser: 1001 - -## Cluster settings -cluster: - enabled: true - slaveCount: 3 - -## Use redis sentinel in the redis pod. This will disable the master and slave services and -## create one redis service with ports to the sentinel and the redis instances -sentinel: - enabled: false - ## Bitnami Redis Sentintel image version - ## ref: https://hub.docker.com/r/bitnami/redis-sentinel/tags/ - ## - image: - registry: docker.io - repository: bitnami/redis-sentinel - ## Bitnami Redis image tag - ## ref: https://github.com/bitnami/bitnami-docker-redis-sentinel#supported-tags-and-respective-dockerfile-links - ## - tag: 5.0.5-debian-9-r37 - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - masterSet: mymaster - initialCheckTimeout: 5 - quorum: 2 - downAfterMilliseconds: 60000 - failoverTimeout: 18000 - parallelSyncs: 1 - port: 26379 - ## Configure extra options for Redis Sentinel liveness and readiness probes - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) - ## - livenessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 5 - readinessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 5 - ## Redis Sentinel resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - # resources: - # requests: - # memory: 256Mi - # cpu: 100m - ## Redis Sentinel Service properties - service: - ## Redis Sentinel Service type - type: ClusterIP - sentinelPort: 26379 - redisPort: 6379 - - ## Specify the nodePort value for the LoadBalancer and NodePort service types. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - ## - # sentinelNodePort: - # redisNodePort: - - ## Provide any additional annotations which may be required. This can be used to - ## set the LoadBalancer service type to internal only. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer - ## - annotations: {} - loadBalancerIP: - -networkPolicy: - ## Specifies whether a NetworkPolicy should be created - ## - enabled: true - - ## The Policy model to apply. When set to false, only pods with the correct - ## client label will have network access to the port Redis is listening - ## on. When true, Redis will accept connections from any source - ## (with the correct destination port). - ## - # allowExternal: true - -serviceAccount: - ## Specifies whether a ServiceAccount should be created - ## - create: false - ## The name of the ServiceAccount to use. - ## If not set and create is true, a name is generated using the fullname template - name: - -rbac: - ## Specifies whether RBAC resources should be created - ## - create: false - - role: - ## Rules to create. It follows the role specification - # rules: - # - apiGroups: - # - extensions - # resources: - # - podsecuritypolicies - # verbs: - # - use - # resourceNames: - # - gce.unprivileged - rules: [] - - -## Use password authentication -usePassword: true -## Redis password (both master and slave) -## Defaults to a random 10-character alphanumeric string if not set and usePassword is true -## ref: https://github.com/bitnami/bitnami-docker-redis#setting-the-server-password-on-first-run -## -password: -## Use existing secret (ignores previous password) -# existingSecret: -## Password key to be retrieved from Redis secret -## -# existingSecretPasswordKey: - -## Mount secrets as files instead of environment variables -usePasswordFile: false - -## Persist data to a persistent volume -persistence: {} - ## A manually managed Persistent Volume and Claim - ## Requires persistence.enabled: true - ## If defined, PVC must be created manually before volume will be bound - # existingClaim: - -# Redis port -redisPort: 6379 - -## -## Redis Master parameters -## -master: - ## Redis command arguments - ## - ## Can be used to specify command line arguments, for example: - ## - command: "/run.sh" - ## Redis additional command line flags - ## - ## Can be used to specify command line flags, for example: - ## - ## extraFlags: - ## - "--maxmemory-policy volatile-ttl" - ## - "--repl-backlog-size 1024mb" - extraFlags: [] - ## Comma-separated list of Redis commands to disable - ## - ## Can be used to disable Redis commands for security reasons. - ## Commands will be completely disabled by renaming each to an empty string. - ## ref: https://redis.io/topics/security#disabling-of-specific-commands - ## - disableCommands: - - FLUSHDB - - FLUSHALL - - ## Redis Master additional pod labels and annotations - ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ - podLabels: {} - podAnnotations: {} - - ## Redis Master resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - # resources: - # requests: - # memory: 256Mi - # cpu: 100m - ## Use an alternate scheduler, e.g. "stork". - ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ - ## - # schedulerName: - - ## Configure extra options for Redis Master liveness and readiness probes - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) - ## - livenessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 5 - readinessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 5 - - ## Redis Master Node selectors and tolerations for pod assignment - ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector - ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#taints-and-tolerations-beta-feature - ## - # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} - # tolerations: [] - ## Redis Master pod/node affinity/anti-affinity - ## - affinity: {} - - ## Redis Master Service properties - service: - ## Redis Master Service type - type: ClusterIP - port: 6379 - - ## Specify the nodePort value for the LoadBalancer and NodePort service types. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - ## - # nodePort: - - ## Provide any additional annotations which may be required. This can be used to - ## set the LoadBalancer service type to internal only. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer - ## - annotations: {} - loadBalancerIP: - - ## Enable persistence using Persistent Volume Claims - ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ - ## - persistence: - enabled: true - ## The path the volume will be mounted at, useful when using different - ## Redis images. - path: /data - ## The subdirectory of the volume to mount to, useful in dev environments - ## and one PV for multiple services. - subPath: "" - ## redis data Persistent Volume Storage Class - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "-" - accessModes: - - ReadWriteOnce - size: 8Gi - - ## Update strategy, can be set to RollingUpdate or onDelete by default. - ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets - statefulset: - updateStrategy: RollingUpdate - ## Partition update strategy - ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions - # rollingUpdatePartition: - - ## Redis Master pod priorityClassName - # priorityClassName: {} - - -## -## Redis Slave properties -## Note: service.type is a mandatory parameter -## The rest of the parameters are either optional or, if undefined, will inherit those declared in Redis Master -## -slave: - ## Slave Service properties - service: - ## Redis Slave Service type - type: ClusterIP - ## Redis port - port: 6379 - ## Specify the nodePort value for the LoadBalancer and NodePort service types. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - ## - # nodePort: - - ## Provide any additional annotations which may be required. This can be used to - ## set the LoadBalancer service type to internal only. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer - ## - annotations: {} - loadBalancerIP: - - ## Redis slave port - port: 6379 - - ## Can be used to specify command line arguments, for example: - ## - command: "/run.sh" - ## Redis extra flags - extraFlags: [] - ## List of Redis commands to disable - disableCommands: - - FLUSHDB - - FLUSHALL - - ## Redis Slave pod/node affinity/anti-affinity - ## - affinity: {} - - ## Configure extra options for Redis Slave liveness and readiness probes - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) - ## - livenessProbe: - enabled: true - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 5 - readinessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 10 - timeoutSeconds: 10 - successThreshold: 1 - failureThreshold: 5 - - ## Redis slave Resource - # resources: - # requests: - # memory: 256Mi - # cpu: 100m - - ## Enable persistence using Persistent Volume Claims - ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ - ## - persistence: - enabled: true - ## The path the volume will be mounted at, useful when using different - ## Redis images. - path: /data - ## The subdirectory of the volume to mount to, useful in dev environments - ## and one PV for multiple services. - subPath: "" - ## redis data Persistent Volume Storage Class - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "-" - accessModes: - - ReadWriteOnce - size: 8Gi - - ## Update strategy, can be set to RollingUpdate or onDelete by default. - ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets - statefulset: - updateStrategy: RollingUpdate - ## Partition update strategy - ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions - # rollingUpdatePartition: - - ## Redis slave selectors and tolerations for pod assignment - # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} - # tolerations: [] - - ## Use an alternate scheduler, e.g. "stork". - ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ - ## - # schedulerName: - - ## Redis slave pod Annotation and Labels - podLabels: {} - podAnnotations: {} - - ## Redis slave pod priorityClassName - # priorityClassName: {} - -## Prometheus Exporter / Metrics -## -metrics: - enabled: true - - image: - registry: docker.io - repository: bitnami/redis-exporter - tag: 1.0.3-debian-9-r0 - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - - ## Metrics exporter resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - ## - # resources: {} - - ## Extra arguments for Metrics exporter, for example: - ## extraArgs: - ## check-keys: myKey,myOtherKey - # extraArgs: {} - ## Metrics exporter pod priorityClassName - # priorityClassName: {} - service: - type: ClusterIP - ## Use serviceLoadBalancerIP to request a specific static IP, - ## otherwise leave blank - # loadBalancerIP: - annotations: {} - - ## Metrics exporter pod Annotation and Labels - podAnnotations: - prometheus.io/scrape: "true" - prometheus.io/port: "9121" - # podLabels: {} - - # Enable this if you're using https://github.com/coreos/prometheus-operator - serviceMonitor: - enabled: false - ## Specify a namespace if needed - # namespace: monitoring - # fallback to the prometheus default unless specified - # interval: 10s - ## Defaults to what's used if you follow CoreOS [Prometheus Install Instructions](https://github.com/helm/charts/tree/master/stable/prometheus-operator#tldr) - ## [Prometheus Selector Label](https://github.com/helm/charts/tree/master/stable/prometheus-operator#prometheus-operator-1) - ## [Kube Prometheus Selector Label](https://github.com/helm/charts/tree/master/stable/prometheus-operator#exporters) - selector: - prometheus: kube-prometheus - -## -## Init containers parameters: -## volumePermissions: Change the owner of the persist volume mountpoint to RunAsUser:fsGroup -## -volumePermissions: - enabled: false - image: - registry: docker.io - repository: bitnami/minideb - tag: stretch - pullPolicy: Always - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - resources: {} - # resources: - # requests: - # memory: 128Mi - # cpu: 100m - -## Redis config file -## ref: https://redis.io/topics/config -## -configmap: |- - # maxmemory-policy volatile-lru - -## Sysctl InitContainer -## used to perform sysctl operation to modify Kernel settings (needed sometimes to avoid warnings) -sysctlImage: - enabled: false - command: [] - registry: docker.io - repository: bitnami/minideb - tag: stretch - pullPolicy: Always - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - mountHostSys: false - resources: {} - # resources: - # requests: - # memory: 128Mi - # cpu: 100m diff --git a/task5/APP-helm/charts/redis/ci/redis-lib-values.yaml b/task5/APP-helm/charts/redis/ci/redis-lib-values.yaml deleted file mode 100755 index e03382b..0000000 --- a/task5/APP-helm/charts/redis/ci/redis-lib-values.yaml +++ /dev/null @@ -1,13 +0,0 @@ -## Redis library image -## ref: https://hub.docker.com/r/library/redis/ -## -image: - registry: docker.io - repository: redis - tag: '5.0.5' - -master: - command: "redis-server" - -slave: - command: "redis-server" diff --git a/task5/APP-helm/charts/redis/ci/redisgraph-module-values.yaml b/task5/APP-helm/charts/redis/ci/redisgraph-module-values.yaml deleted file mode 100755 index 8096020..0000000 --- a/task5/APP-helm/charts/redis/ci/redisgraph-module-values.yaml +++ /dev/null @@ -1,10 +0,0 @@ -image: - registry: docker.io - repository: redislabs/redisgraph - tag: '1.0.0' - -master: - command: "redis-server" - -slave: - command: "redis-server" diff --git a/task5/APP-helm/charts/redis/templates/NOTES.txt b/task5/APP-helm/charts/redis/templates/NOTES.txt deleted file mode 100755 index f3b46e1..0000000 --- a/task5/APP-helm/charts/redis/templates/NOTES.txt +++ /dev/null @@ -1,104 +0,0 @@ -** Please be patient while the chart is being deployed ** - -{{- if contains .Values.master.service.type "LoadBalancer" }} -{{- if not .Values.usePassword }} -{{ if and (not .Values.networkPolicy.enabled) (.Values.networkPolicy.allowExternal) }} - -------------------------------------------------------------------------------- - WARNING - - By specifying "master.service.type=LoadBalancer" and "usePassword=false" you have - most likely exposed the Redis service externally without any authentication - mechanism. - - For security reasons, we strongly suggest that you switch to "ClusterIP" or - "NodePort". As alternative, you can also switch to "usePassword=true" - providing a valid password on "password" parameter. - -------------------------------------------------------------------------------- -{{- end }} -{{- end }} -{{- end }} - -{{- if .Values.cluster.enabled }} -{{- if .Values.sentinel.enabled }} -Redis can be accessed via port {{ .Values.sentinel.service.redisPort }} on the following DNS name from within your cluster: - -{{ template "redis.fullname" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} for read only operations - -For read/write operations, first access the Redis Sentinel cluster, which is available in port {{ .Values.sentinel.service.sentinelPort }} using the same domain name above. - -{{- else }} -Redis can be accessed via port {{ .Values.redisPort }} on the following DNS names from within your cluster: - -{{ template "redis.fullname" . }}-master.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} for read/write operations -{{ template "redis.fullname" . }}-slave.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} for read-only operations -{{- end }} - -{{- else }} -Redis can be accessed via port {{ .Values.redisPort }} on the following DNS name from within your cluster: - -{{ template "redis.fullname" . }}-master.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} - -{{- end }} - -{{ if .Values.usePassword }} -To get your password run: - - export REDIS_PASSWORD=$(kubectl get secret --namespace {{ .Release.Namespace }} {{ template "redis.secretName" . }} -o jsonpath="{.data.redis-password}" | base64 --decode) -{{- end }} - -To connect to your Redis server: - -1. Run a Redis pod that you can use as a client: - - kubectl run --namespace {{ .Release.Namespace }} {{ template "redis.fullname" . }}-client --rm --tty -i --restart='Never' \ - {{ if .Values.usePassword }} --env REDIS_PASSWORD=$REDIS_PASSWORD \{{ end }} - {{- if and (.Values.networkPolicy.enabled) (not .Values.networkPolicy.allowExternal) }}--labels="{{ template "redis.name" . }}-client=true" \{{- end }} - --image {{ template "redis.image" . }} -- bash - -2. Connect using the Redis CLI: - -{{- if .Values.cluster.enabled }} - {{- if .Values.sentinel.enabled }} - redis-cli -h {{ template "redis.fullname" . }} -p {{ .Values.sentinel.service.redisPort }}{{ if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }} # Read only operations - redis-cli -h {{ template "redis.fullname" . }} -p {{ .Values.sentinel.service.sentinelPort }}{{ if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }} # Sentinel access - {{- else }} - redis-cli -h {{ template "redis.fullname" . }}-master{{ if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }} - redis-cli -h {{ template "redis.fullname" . }}-slave{{ if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }} - {{- end }} -{{- else }} - redis-cli -h {{ template "redis.fullname" . }}-master{{ if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }} -{{- end }} - -{{ if and (.Values.networkPolicy.enabled) (not .Values.networkPolicy.allowExternal) }} -Note: Since NetworkPolicy is enabled, only pods with label -{{ template "redis.fullname" . }}-client=true" -will be able to connect to redis. -{{- else -}} - -To connect to your database from outside the cluster execute the following commands: - -{{- if contains "NodePort" .Values.master.service.type }} - - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "redis.fullname" . }}-master) - redis-cli -h $NODE_IP -p $NODE_PORT {{- if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }} - -{{- else if contains "LoadBalancer" .Values.master.service.type }} - - NOTE: It may take a few minutes for the LoadBalancer IP to be available. - Watch the status with: 'kubectl get svc --namespace {{ .Release.Namespace }} -w {{ template "redis.fullname" . }}' - - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "redis.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") - redis-cli -h $SERVICE_IP -p {{ .Values.master.service.nodePort }} {{- if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }} - -{{- else if contains "ClusterIP" .Values.master.service.type }} - - kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ template "redis.fullname" . }}-master {{ .Values.redisPort }}:{{ .Values.redisPort }} & - redis-cli -h 127.0.0.1 -p {{ .Values.redisPort }} {{- if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }} - -{{- end }} -{{- end }} - -{{ include "redis.checkRollingTags" . }} diff --git a/task5/APP-helm/charts/redis/templates/_helpers.tpl b/task5/APP-helm/charts/redis/templates/_helpers.tpl deleted file mode 100755 index be47dd8..0000000 --- a/task5/APP-helm/charts/redis/templates/_helpers.tpl +++ /dev/null @@ -1,320 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{- define "redis.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Expand the chart plus release name (used by the chart label) -*/}} -{{- define "redis.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version -}} -{{- end -}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "redis.fullname" -}} -{{- if .Values.fullnameOverride -}} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- if contains $name .Release.Name -}} -{{- .Release.Name | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} -{{- end -}} - -{{/* -Return the appropriate apiVersion for networkpolicy. -*/}} -{{- define "networkPolicy.apiVersion" -}} -{{- if semverCompare ">=1.4-0, <1.7-0" .Capabilities.KubeVersion.GitVersion -}} -{{- print "extensions/v1beta1" -}} -{{- else -}} -{{- print "networking.k8s.io/v1" -}} -{{- end -}} -{{- end -}} - -{{/* -Return the proper Redis image name -*/}} -{{- define "redis.image" -}} -{{- $registryName := .Values.image.registry -}} -{{- $repositoryName := .Values.image.repository -}} -{{- $tag := .Values.image.tag | toString -}} -{{/* -Helm 2.11 supports the assignment of a value to a variable defined in a different scope, -but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. -Also, we can't use a single if because lazy evaluation is not an option -*/}} -{{- if .Values.global }} - {{- if .Values.global.imageRegistry }} - {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} - {{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} - {{- end -}} -{{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} -{{- end -}} -{{- end -}} - -{{/* -Return the proper Redis Sentinel image name -*/}} -{{- define "sentinel.image" -}} -{{- $registryName := .Values.sentinel.image.registry -}} -{{- $repositoryName := .Values.sentinel.image.repository -}} -{{- $tag := .Values.sentinel.image.tag | toString -}} -{{/* -Helm 2.11 supports the assignment of a value to a variable defined in a different scope, -but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. -Also, we can't use a single if because lazy evaluation is not an option -*/}} -{{- if .Values.global }} - {{- if .Values.global.imageRegistry }} - {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} - {{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} - {{- end -}} -{{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} -{{- end -}} -{{- end -}} - -{{/* -Return the proper image name (for the metrics image) -*/}} -{{- define "redis.metrics.image" -}} -{{- $registryName := .Values.metrics.image.registry -}} -{{- $repositoryName := .Values.metrics.image.repository -}} -{{- $tag := .Values.metrics.image.tag | toString -}} -{{/* -Helm 2.11 supports the assignment of a value to a variable defined in a different scope, -but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. -Also, we can't use a single if because lazy evaluation is not an option -*/}} -{{- if .Values.global }} - {{- if .Values.global.imageRegistry }} - {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} - {{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} - {{- end -}} -{{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} -{{- end -}} -{{- end -}} - -{{/* -Return the proper image name (for the init container volume-permissions image) -*/}} -{{- define "redis.volumePermissions.image" -}} -{{- $registryName := .Values.volumePermissions.image.registry -}} -{{- $repositoryName := .Values.volumePermissions.image.repository -}} -{{- $tag := .Values.volumePermissions.image.tag | toString -}} -{{/* -Helm 2.11 supports the assignment of a value to a variable defined in a different scope, -but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. -Also, we can't use a single if because lazy evaluation is not an option -*/}} -{{- if .Values.global }} - {{- if .Values.global.imageRegistry }} - {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} - {{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} - {{- end -}} -{{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} -{{- end -}} -{{- end -}} - -{{/* -Create the name of the service account to use -*/}} -{{- define "redis.serviceAccountName" -}} -{{- if .Values.serviceAccount.create -}} - {{ default (include "redis.fullname" .) .Values.serviceAccount.name }} -{{- else -}} - {{ default "default" .Values.serviceAccount.name }} -{{- end -}} -{{- end -}} - -{{/* -Get the password secret. -*/}} -{{- define "redis.secretName" -}} -{{- if .Values.existingSecret -}} -{{- printf "%s" .Values.existingSecret -}} -{{- else -}} -{{- printf "%s" (include "redis.fullname" .) -}} -{{- end -}} -{{- end -}} - -{{/* -Get the password key to be retrieved from Redis secret. -*/}} -{{- define "redis.secretPasswordKey" -}} -{{- if and .Values.existingSecret .Values.existingSecretPasswordKey -}} -{{- printf "%s" .Values.existingSecretPasswordKey -}} -{{- else -}} -{{- printf "redis-password" -}} -{{- end -}} -{{- end -}} - -{{/* -Return sysctl image -*/}} -{{- define "redis.sysctl.image" -}} -{{- $registryName := default "docker.io" .Values.sysctlImage.registry -}} -{{- $repositoryName := .Values.sysctlImage.repository -}} -{{- $tag := default "stretch" .Values.sysctlImage.tag | toString -}} -{{/* -Helm 2.11 supports the assignment of a value to a variable defined in a different scope, -but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. -Also, we can't use a single if because lazy evaluation is not an option -*/}} -{{- if .Values.global }} - {{- if .Values.global.imageRegistry }} - {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} - {{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} - {{- end -}} -{{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} -{{- end -}} -{{- end -}} - -{{/* -Return the proper Docker Image Registry Secret Names -*/}} -{{- define "redis.imagePullSecrets" -}} -{{/* -Helm 2.11 supports the assignment of a value to a variable defined in a different scope, -but Helm 2.9 and 2.10 does not support it, so we need to implement this if-else logic. -Also, we can not use a single if because lazy evaluation is not an option -*/}} -{{- if .Values.global }} -{{- if .Values.global.imagePullSecrets }} -imagePullSecrets: -{{- range .Values.global.imagePullSecrets }} - - name: {{ . }} -{{- end }} -{{- else if or .Values.image.pullSecrets .Values.metrics.image.pullSecrets .Values.sysctlImage.pullSecrets .Values.volumePermissions.image.pullSecrets }} -imagePullSecrets: -{{- range .Values.image.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- range .Values.metrics.image.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- range .Values.sysctlImage.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- range .Values.volumePermissions.image.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- end -}} -{{- else if or .Values.image.pullSecrets .Values.metrics.image.pullSecrets .Values.sysctlImage.pullSecrets .Values.volumePermissions.image.pullSecrets }} -imagePullSecrets: -{{- range .Values.image.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- range .Values.metrics.image.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- range .Values.sysctlImage.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- range .Values.volumePermissions.image.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- end -}} -{{- end -}} - -{{/* Check if there are rolling tags in the images */}} -{{- define "redis.checkRollingTags" -}} -{{- if and (contains "bitnami/" .Values.image.repository) (not (.Values.image.tag | toString | regexFind "-r\\d+$|sha256:")) }} -WARNING: Rolling tag detected ({{ .Values.image.repository }}:{{ .Values.image.tag }}), please note that it is strongly recommended to avoid using rolling tags in a production environment. -+info https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/ -{{- end }} -{{- if and (contains "bitnami/" .Values.sentinel.image.repository) (not (.Values.sentinel.image.tag | toString | regexFind "-r\\d+$|sha256:")) }} -WARNING: Rolling tag detected ({{ .Values.sentinel.image.repository }}:{{ .Values.sentinel.image.tag }}), please note that it is strongly recommended to avoid using rolling tags in a production environment. -+info https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/ -{{- end }} -{{- end -}} - -{{/* -Return the proper Storage Class for master -*/}} -{{- define "redis.master.storageClass" -}} -{{/* -Helm 2.11 supports the assignment of a value to a variable defined in a different scope, -but Helm 2.9 and 2.10 does not support it, so we need to implement this if-else logic. -*/}} -{{- if .Values.global -}} - {{- if .Values.global.storageClass -}} - {{- if (eq "-" .Values.global.storageClass) -}} - {{- printf "storageClassName: \"\"" -}} - {{- else }} - {{- printf "storageClassName: %s" .Values.global.storageClass -}} - {{- end -}} - {{- else -}} - {{- if .Values.master.persistence.storageClass -}} - {{- if (eq "-" .Values.master.persistence.storageClass) -}} - {{- printf "storageClassName: \"\"" -}} - {{- else }} - {{- printf "storageClassName: %s" .Values.master.persistence.storageClass -}} - {{- end -}} - {{- end -}} - {{- end -}} -{{- else -}} - {{- if .Values.master.persistence.storageClass -}} - {{- if (eq "-" .Values.master.persistence.storageClass) -}} - {{- printf "storageClassName: \"\"" -}} - {{- else }} - {{- printf "storageClassName: %s" .Values.master.persistence.storageClass -}} - {{- end -}} - {{- end -}} -{{- end -}} -{{- end -}} - -{{/* -Return the proper Storage Class for slave -*/}} -{{- define "redis.slave.storageClass" -}} -{{/* -Helm 2.11 supports the assignment of a value to a variable defined in a different scope, -but Helm 2.9 and 2.10 does not support it, so we need to implement this if-else logic. -*/}} -{{- if .Values.global -}} - {{- if .Values.global.storageClass -}} - {{- if (eq "-" .Values.global.storageClass) -}} - {{- printf "storageClassName: \"\"" -}} - {{- else }} - {{- printf "storageClassName: %s" .Values.global.storageClass -}} - {{- end -}} - {{- else -}} - {{- if .Values.slave.persistence.storageClass -}} - {{- if (eq "-" .Values.slave.persistence.storageClass) -}} - {{- printf "storageClassName: \"\"" -}} - {{- else }} - {{- printf "storageClassName: %s" .Values.slave.persistence.storageClass -}} - {{- end -}} - {{- end -}} - {{- end -}} -{{- else -}} - {{- if .Values.slave.persistence.storageClass -}} - {{- if (eq "-" .Values.slave.persistence.storageClass) -}} - {{- printf "storageClassName: \"\"" -}} - {{- else }} - {{- printf "storageClassName: %s" .Values.slave.persistence.storageClass -}} - {{- end -}} - {{- end -}} -{{- end -}} -{{- end -}} diff --git a/task5/APP-helm/charts/redis/templates/configmap.yaml b/task5/APP-helm/charts/redis/templates/configmap.yaml deleted file mode 100755 index d17ec26..0000000 --- a/task5/APP-helm/charts/redis/templates/configmap.yaml +++ /dev/null @@ -1,52 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "redis.fullname" . }} - labels: - app: {{ template "redis.name" . }} - chart: {{ template "redis.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -data: - redis.conf: |- -{{- if .Values.configmap }} - # User-supplied configuration: -{{ tpl .Values.configmap . | indent 4 }} -{{- end }} - master.conf: |- - dir {{ .Values.master.persistence.path }} -{{- if .Values.master.configmap }} - # User-supplied master configuration: -{{ tpl .Values.master.configmap . | indent 4 }} -{{- end }} -{{- if .Values.master.disableCommands }} -{{- range .Values.master.disableCommands }} - rename-command {{ . }} "" -{{- end }} -{{- end }} - replica.conf: |- - dir {{ .Values.slave.persistence.path }} - slave-read-only yes -{{- if .Values.slave.configmap }} - # User-supplied slave configuration: -{{ tpl .Values.slave.configmap . | indent 4 }} -{{- end }} -{{- if .Values.slave.disableCommands }} -{{- range .Values.slave.disableCommands }} - rename-command {{ . }} "" -{{- end }} -{{- end }} -{{- if .Values.sentinel.enabled }} - sentinel.conf: |- - dir "/tmp" - bind 0.0.0.0 - port {{ .Values.sentinel.port }} - sentinel monitor {{ .Values.sentinel.masterSet }} {{ template "redis.fullname" . }}-master-0.{{ template "redis.fullname" . }}-headless.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} {{ .Values.redisPort }} {{ .Values.sentinel.quorum }} - sentinel down-after-milliseconds {{ .Values.sentinel.masterSet }} {{ .Values.sentinel.downAfterMilliseconds }} - sentinel failover-timeout {{ .Values.sentinel.masterSet }} {{ .Values.sentinel.failoverTimeout }} - sentinel parallel-syncs {{ .Values.sentinel.masterSet }} {{ .Values.sentinel.parallelSyncs }} -{{- if .Values.sentinel.configmap }} - # User-supplied sentinel configuration: -{{ tpl .Values.sentinel.configmap . | indent 4 }} -{{- end }} -{{- end }} diff --git a/task5/APP-helm/charts/redis/templates/headless-svc.yaml b/task5/APP-helm/charts/redis/templates/headless-svc.yaml deleted file mode 100755 index 909cbce..0000000 --- a/task5/APP-helm/charts/redis/templates/headless-svc.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ template "redis.fullname" . }}-headless - labels: - app: {{ template "redis.name" . }} - chart: {{ template "redis.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -spec: - type: ClusterIP - clusterIP: None - ports: - - name: redis - port: {{ .Values.redisPort }} - targetPort: redis -{{- if .Values.sentinel.enabled }} - - name: redis-sentinel - port: {{ .Values.sentinel.port }} - targetPort: redis-sentinel -{{- end }} - selector: - app: {{ template "redis.name" . }} - release: {{ .Release.Name }} diff --git a/task5/APP-helm/charts/redis/templates/health-configmap.yaml b/task5/APP-helm/charts/redis/templates/health-configmap.yaml deleted file mode 100755 index 35c61b5..0000000 --- a/task5/APP-helm/charts/redis/templates/health-configmap.yaml +++ /dev/null @@ -1,134 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "redis.fullname" . }}-health - labels: - app: {{ template "redis.name" . }} - chart: {{ template "redis.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -data: - ping_readiness_local.sh: |- -{{- if .Values.usePasswordFile }} - password_aux=`cat ${REDIS_PASSWORD_FILE}` - export REDIS_PASSWORD=$password_aux -{{- end }} - response=$( - timeout -s 9 $1 \ - redis-cli \ -{{- if .Values.usePassword }} - -a $REDIS_PASSWORD --no-auth-warning \ -{{- end }} - -h localhost \ - -p $REDIS_PORT \ - ping - ) - if [ "$response" != "PONG" ]; then - echo "$response" - exit 1 - fi - ping_liveness_local.sh: |- -{{- if .Values.usePasswordFile }} - password_aux=`cat ${REDIS_PASSWORD_FILE}` - export REDIS_PASSWORD=$password_aux -{{- end }} - response=$( - timeout -s 9 $1 \ - redis-cli \ -{{- if .Values.usePassword }} - -a $REDIS_PASSWORD --no-auth-warning \ -{{- end }} - -h localhost \ - -p $REDIS_PORT \ - ping - ) - if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading the dataset in memory" ]; then - echo "$response" - exit 1 - fi -{{- if .Values.sentinel.enabled }} - ping_sentinel.sh: |- -{{- if .Values.usePasswordFile }} - password_aux=`cat ${REDIS_PASSWORD_FILE}` - export REDIS_PASSWORD=$password_aux -{{- end }} - response=$( - timeout -s 9 $1 \ - redis-cli \ -{{- if .Values.usePassword }} - -a $REDIS_PASSWORD --no-auth-warning \ -{{- end }} - -h localhost \ - -p $REDIS_SENTINEL_PORT \ - ping - ) - if [ "$response" != "PONG" ]; then - echo "$response" - exit 1 - fi - parse_sentinels.awk: |- - /ip/ {FOUND_IP=1} - /port/ {FOUND_PORT=1} - /runid/ {FOUND_RUNID=1} - !/ip|port|runid/ { - if (FOUND_IP==1) { - IP=$1; FOUND_IP=0; - } - else if (FOUND_PORT==1) { - PORT=$1; - FOUND_PORT=0; - } else if (FOUND_RUNID==1) { - printf "\nsentinel known-sentinel {{ .Values.sentinel.masterSet }} %s %s %s", IP, PORT, $0; FOUND_RUNID=0; - } - } -{{- end }} - ping_readiness_master.sh: |- -{{- if .Values.usePasswordFile }} - password_aux=`cat ${REDIS_MASTER_PASSWORD_FILE}` - export REDIS_MASTER_PASSWORD=$password_aux -{{- end }} - response=$( - timeout -s 9 $1 \ - redis-cli \ -{{- if .Values.usePassword }} - -a $REDIS_MASTER_PASSWORD --no-auth-warning \ -{{- end }} - -h $REDIS_MASTER_HOST \ - -p $REDIS_MASTER_PORT_NUMBER \ - ping - ) - if [ "$response" != "PONG" ]; then - echo "$response" - exit 1 - fi - ping_liveness_master.sh: |- -{{- if .Values.usePasswordFile }} - password_aux=`cat ${REDIS_MASTER_PASSWORD_FILE}` - export REDIS_MASTER_PASSWORD=$password_aux -{{- end }} - response=$( - timeout -s 9 $1 \ - redis-cli \ -{{- if .Values.usePassword }} - -a $REDIS_MASTER_PASSWORD --no-auth-warning \ -{{- end }} - -h $REDIS_MASTER_HOST \ - -p $REDIS_MASTER_PORT_NUMBER \ - ping - ) - if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading the dataset in memory" ]; then - echo "$response" - exit 1 - fi - ping_readiness_local_and_master.sh: |- - script_dir="$(dirname "$0")" - exit_status=0 - "$script_dir/ping_readiness_local.sh" $1 || exit_status=$? - "$script_dir/ping_readiness_master.sh" $1 || exit_status=$? - exit $exit_status - ping_liveness_local_and_master.sh: |- - script_dir="$(dirname "$0")" - exit_status=0 - "$script_dir/ping_liveness_local.sh" $1 || exit_status=$? - "$script_dir/ping_liveness_master.sh" $1 || exit_status=$? - exit $exit_status diff --git a/task5/APP-helm/charts/redis/templates/metrics-prometheus.yaml b/task5/APP-helm/charts/redis/templates/metrics-prometheus.yaml deleted file mode 100755 index 3f33454..0000000 --- a/task5/APP-helm/charts/redis/templates/metrics-prometheus.yaml +++ /dev/null @@ -1,30 +0,0 @@ -{{- if and (.Values.metrics.enabled) (.Values.metrics.serviceMonitor.enabled) }} -apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor -metadata: - name: {{ template "redis.fullname" . }} - {{- if .Values.metrics.serviceMonitor.namespace }} - namespace: {{ .Values.metrics.serviceMonitor.namespace }} - {{- end }} - labels: - app: {{ template "redis.name" . }} - chart: {{ template "redis.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} - {{- range $key, $value := .Values.metrics.serviceMonitor.selector }} - {{ $key }}: {{ $value | quote }} - {{- end }} -spec: - endpoints: - - port: metrics - {{- if .Values.metrics.serviceMonitor.interval }} - interval: {{ .Values.metrics.serviceMonitor.interval }} - {{- end }} - selector: - matchLabels: - app: {{ template "redis.name" . }} - release: {{ .Release.Name }} - namespaceSelector: - matchNames: - - {{ .Release.Namespace }} -{{- end -}} diff --git a/task5/APP-helm/charts/redis/templates/metrics-svc.yaml b/task5/APP-helm/charts/redis/templates/metrics-svc.yaml deleted file mode 100755 index ef39725..0000000 --- a/task5/APP-helm/charts/redis/templates/metrics-svc.yaml +++ /dev/null @@ -1,30 +0,0 @@ -{{- if .Values.metrics.enabled }} -apiVersion: v1 -kind: Service -metadata: - name: {{ template "redis.fullname" . }}-metrics - labels: - app: {{ template "redis.name" . }} - chart: {{ template "redis.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} - {{- if .Values.metrics.service.labels -}} - {{ toYaml .Values.metrics.service.labels | nindent 4 }} - {{- end -}} - {{- if .Values.metrics.service.annotations }} - annotations: {{ toYaml .Values.metrics.service.annotations | nindent 4 }} - {{- end }} -spec: - type: {{ .Values.metrics.service.type }} - {{ if eq .Values.metrics.service.type "LoadBalancer" -}} {{ if .Values.metrics.service.loadBalancerIP -}} - loadBalancerIP: {{ .Values.metrics.service.loadBalancerIP }} - {{ end -}} - {{- end -}} - ports: - - name: metrics - port: 9121 - targetPort: metrics - selector: - app: {{ template "redis.name" . }} - release: {{ .Release.Name }} -{{- end }} diff --git a/task5/APP-helm/charts/redis/templates/networkpolicy.yaml b/task5/APP-helm/charts/redis/templates/networkpolicy.yaml deleted file mode 100755 index 8c6b14a..0000000 --- a/task5/APP-helm/charts/redis/templates/networkpolicy.yaml +++ /dev/null @@ -1,65 +0,0 @@ -{{- if .Values.networkPolicy.enabled }} -kind: NetworkPolicy -apiVersion: {{ template "networkPolicy.apiVersion" . }} -metadata: - name: {{ template "redis.fullname" . }} - labels: - app: {{ template "redis.name" . }} - chart: {{ template "redis.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -spec: - podSelector: - matchLabels: - app: {{ template "redis.name" . }} - release: {{ .Release.Name }} - {{- if .Values.cluster.enabled }} - policyTypes: - - Ingress - - Egress - egress: - # Allow outbound connections to other cluster pods - - ports: - - port: {{ .Values.redisPort }} - {{- if .Values.sentinel.enabled }} - - port: {{ .Values.sentinel.port }} - {{- end }} - to: - - podSelector: - matchLabels: - app: {{ template "redis.name" . }} - release: {{ .Release.Name }} - {{- end }} - ingress: - # Allow inbound connections - - ports: - - port: {{ .Values.redisPort }} - {{- if .Values.sentinel.enabled }} - - port: {{ .Values.sentinel.port }} - {{- end }} - {{- if not .Values.networkPolicy.allowExternal }} - from: - - podSelector: - matchLabels: - {{ template "redis.fullname" . }}-client: "true" - {{- if .Values.metrics.enabled }} - - podSelector: - matchLabels: - app: {{ template "redis.name" . }} - release: {{ .Release.Name }} - role: metrics - {{- end }} - {{- if .Values.cluster.enabled }} - - podSelector: - matchLabels: - app: {{ template "redis.name" . }} - release: {{ .Release.Name }} - role: slave - {{- end }} - {{- end }} - {{- if .Values.metrics.enabled }} - # Allow prometheus scrapes for metrics - - ports: - - port: 9121 - {{- end }} -{{- end }} diff --git a/task5/APP-helm/charts/redis/templates/redis-master-statefulset.yaml b/task5/APP-helm/charts/redis/templates/redis-master-statefulset.yaml deleted file mode 100755 index 2e67493..0000000 --- a/task5/APP-helm/charts/redis/templates/redis-master-statefulset.yaml +++ /dev/null @@ -1,408 +0,0 @@ -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: {{ template "redis.fullname" . }}-master - labels: - app: {{ template "redis.name" . }} - chart: {{ template "redis.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -spec: - selector: - matchLabels: - app: {{ template "redis.name" . }} - release: {{ .Release.Name }} - role: master - serviceName: {{ template "redis.fullname" . }}-headless - template: - metadata: - labels: - app: {{ template "redis.name" . }} - chart: {{ template "redis.chart" . }} - release: {{ .Release.Name }} - role: master -{{- if .Values.master.podLabels }} -{{ toYaml .Values.master.podLabels | indent 8 }} -{{- end }} -{{- if and .Values.metrics.enabled .Values.metrics.podLabels }} -{{ toYaml .Values.metrics.podLabels | indent 8 }} -{{- end }} - annotations: - checksum/health: {{ include (print $.Template.BasePath "/health-configmap.yaml") . | sha256sum }} - checksum/configmap: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} - checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} - {{- if .Values.master.podAnnotations }} -{{ toYaml .Values.master.podAnnotations | indent 8 }} - {{- end }} - {{- if and .Values.metrics.enabled .Values.metrics.podAnnotations }} -{{ toYaml .Values.metrics.podAnnotations | indent 8 }} - {{- end }} - spec: -{{- include "redis.imagePullSecrets" . | indent 6 }} - {{- if .Values.securityContext.enabled }} - securityContext: - fsGroup: {{ .Values.securityContext.fsGroup }} - {{- if .Values.securityContext.sysctls }} - sysctls: -{{ toYaml .Values.securityContext.sysctls | indent 8 }} - {{- end }} - {{- end }} - serviceAccountName: "{{ template "redis.serviceAccountName" . }}" - {{- if .Values.master.priorityClassName }} - priorityClassName: "{{ .Values.master.priorityClassName }}" - {{- end }} - {{- with .Values.master.affinity }} - affinity: -{{ tpl (toYaml .) $ | indent 8 }} - {{- end }} - {{- if .Values.master.nodeSelector }} - nodeSelector: -{{ toYaml .Values.master.nodeSelector | indent 8 }} - {{- end }} - {{- if .Values.master.tolerations }} - tolerations: -{{ toYaml .Values.master.tolerations | indent 8 }} - {{- end }} - {{- if .Values.master.schedulerName }} - schedulerName: "{{ .Values.master.schedulerName }}" - {{- end }} - containers: - - name: {{ template "redis.fullname" . }} - image: "{{ template "redis.image" . }}" - imagePullPolicy: {{ .Values.image.pullPolicy | quote }} - {{- if .Values.securityContext.enabled }} - securityContext: - runAsUser: {{ .Values.securityContext.runAsUser }} - {{- end }} - command: - - /bin/bash - - -c - - | - {{- if (eq .Values.securityContext.runAsUser 0.0) }} - useradd redis - chown -R redis {{ .Values.master.persistence.path }} - {{- end }} - if [[ -n $REDIS_PASSWORD_FILE ]]; then - password_aux=`cat ${REDIS_PASSWORD_FILE}` - export REDIS_PASSWORD=$password_aux - fi - if [[ ! -f /opt/bitnami/redis/etc/master.conf ]];then - cp /opt/bitnami/redis/mounted-etc/master.conf /opt/bitnami/redis/etc/master.conf - fi - if [[ ! -f /opt/bitnami/redis/etc/redis.conf ]];then - cp /opt/bitnami/redis/mounted-etc/redis.conf /opt/bitnami/redis/etc/redis.conf - fi - ARGS=("--port" "${REDIS_PORT}") - {{- if .Values.usePassword }} - ARGS+=("--requirepass" "${REDIS_PASSWORD}") - ARGS+=("--masterauth" "${REDIS_PASSWORD}") - {{- else }} - ARGS+=("--protected-mode" "no") - {{- end }} - ARGS+=("--include" "/opt/bitnami/redis/etc/redis.conf") - ARGS+=("--include" "/opt/bitnami/redis/etc/master.conf") - {{- if .Values.master.extraFlags }} - {{- range .Values.master.extraFlags }} - ARGS+=({{ . | quote }}) - {{- end }} - {{- end }} - {{- if .Values.master.command }} - {{ .Values.master.command }} ${ARGS[@]} - {{- else }} - redis-server "${ARGS[@]}" - {{- end }} - env: - - name: REDIS_REPLICATION_MODE - value: master - {{- if .Values.usePassword }} - {{- if .Values.usePasswordFile }} - - name: REDIS_PASSWORD_FILE - value: "/opt/bitnami/redis/secrets/redis-password" - {{- else }} - - name: REDIS_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "redis.secretName" . }} - key: {{ template "redis.secretPasswordKey" . }} - {{- end }} - {{- else }} - - name: ALLOW_EMPTY_PASSWORD - value: "yes" - {{- end }} - - name: REDIS_PORT - value: {{ .Values.redisPort | quote }} - ports: - - name: redis - containerPort: {{ .Values.redisPort }} - {{- if .Values.master.livenessProbe.enabled }} - livenessProbe: - initialDelaySeconds: {{ .Values.master.livenessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.master.livenessProbe.periodSeconds }} - timeoutSeconds: {{ .Values.master.livenessProbe.timeoutSeconds }} - successThreshold: {{ .Values.master.livenessProbe.successThreshold }} - failureThreshold: {{ .Values.master.livenessProbe.failureThreshold }} - exec: - command: - - sh - - -c - - /health/ping_liveness_local.sh {{ .Values.master.livenessProbe.timeoutSeconds }} - {{- end }} - {{- if .Values.master.readinessProbe.enabled}} - readinessProbe: - initialDelaySeconds: {{ .Values.master.readinessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.master.readinessProbe.periodSeconds }} - timeoutSeconds: {{ .Values.master.readinessProbe.timeoutSeconds }} - successThreshold: {{ .Values.master.readinessProbe.successThreshold }} - failureThreshold: {{ .Values.master.readinessProbe.failureThreshold }} - exec: - command: - - sh - - -c - - /health/ping_readiness_local.sh {{ .Values.master.livenessProbe.timeoutSeconds }} - {{- end }} - resources: -{{ toYaml .Values.master.resources | indent 10 }} - volumeMounts: - - name: health - mountPath: /health - {{- if .Values.usePasswordFile }} - - name: redis-password - mountPath: /opt/bitnami/redis/secrets/ - {{- end }} - - name: redis-data - mountPath: {{ .Values.master.persistence.path }} - subPath: {{ .Values.master.persistence.subPath }} - - name: config - mountPath: /opt/bitnami/redis/mounted-etc - - name: redis-tmp-conf - mountPath: /opt/bitnami/redis/etc/ - {{- if and .Values.cluster.enabled .Values.sentinel.enabled }} - - name: sentinel - image: "{{ template "sentinel.image" . }}" - imagePullPolicy: {{ .Values.sentinel.image.pullPolicy | quote }} - {{- if .Values.securityContext.enabled }} - securityContext: - runAsUser: {{ .Values.securityContext.runAsUser }} - {{- end }} - command: - - /bin/bash - - -c - - | - if [[ -n $REDIS_PASSWORD_FILE ]]; then - password_aux=`cat ${REDIS_PASSWORD_FILE}` - export REDIS_PASSWORD=$password_aux - fi - if [[ ! -f /opt/bitnami/redis-sentinel/etc/sentinel.conf ]];then - cp /opt/bitnami/redis-sentinel/mounted-etc/sentinel.conf /opt/bitnami/redis-sentinel/etc/sentinel.conf - {{- if .Values.usePassword }} - printf "\nsentinel auth-pass {{ .Values.sentinel.masterSet }} $REDIS_PASSWORD" >> /opt/bitnami/redis-sentinel/etc/sentinel.conf - {{- end }} - fi - echo "Getting information about current running sentinels" - # Get information from existing sentinels - existing_sentinels=$(timeout -s 9 {{ .Values.sentinel.initialCheckTimeout }} redis-cli --raw -h {{ template "redis.fullname" . }} -a "$REDIS_PASSWORD" -p {{ .Values.sentinel.service.sentinelPort }} SENTINEL sentinels {{ .Values.sentinel.masterSet }}) - echo "$existing_sentinels" | awk -f /health/parse_sentinels.awk | tee -a /opt/bitnami/redis-sentinel/etc/sentinel.conf - - redis-server /opt/bitnami/redis-sentinel/etc/sentinel.conf --sentinel - env: - {{- if .Values.usePassword }} - {{- if .Values.usePasswordFile }} - - name: REDIS_PASSWORD_FILE - value: "/opt/bitnami/redis/secrets/redis-password" - {{- else }} - - name: REDIS_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "redis.secretName" . }} - key: {{ template "redis.secretPasswordKey" . }} - {{- end }} - {{- else }} - - name: ALLOW_EMPTY_PASSWORD - value: "yes" - {{- end }} - - name: REDIS_SENTINEL_PORT - value: {{ .Values.sentinel.port | quote }} - ports: - - name: redis-sentinel - containerPort: {{ .Values.sentinel.port }} - {{- if .Values.sentinel.livenessProbe.enabled }} - livenessProbe: - initialDelaySeconds: {{ .Values.sentinel.livenessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.sentinel.livenessProbe.periodSeconds }} - timeoutSeconds: {{ .Values.sentinel.livenessProbe.timeoutSeconds }} - successThreshold: {{ .Values.sentinel.livenessProbe.successThreshold }} - failureThreshold: {{ .Values.sentinel.livenessProbe.failureThreshold }} - exec: - command: - - sh - - -c - - /health/ping_sentinel.sh {{ .Values.sentinel.livenessProbe.timeoutSeconds }} - {{- end }} - {{- if .Values.sentinel.readinessProbe.enabled}} - readinessProbe: - initialDelaySeconds: {{ .Values.sentinel.readinessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.sentinel.readinessProbe.periodSeconds }} - timeoutSeconds: {{ .Values.sentinel.readinessProbe.timeoutSeconds }} - successThreshold: {{ .Values.sentinel.readinessProbe.successThreshold }} - failureThreshold: {{ .Values.sentinel.readinessProbe.failureThreshold }} - exec: - command: - - sh - - -c - - /health/ping_sentinel.sh {{ .Values.sentinel.livenessProbe.timeoutSeconds }} - {{- end }} - resources: -{{ toYaml .Values.sentinel.resources | indent 10 }} - volumeMounts: - - name: health - mountPath: /health - {{- if .Values.usePasswordFile }} - - name: redis-password - mountPath: /opt/bitnami/redis/secrets/ - {{- end }} - - name: redis-data - mountPath: {{ .Values.master.persistence.path }} - subPath: {{ .Values.master.persistence.subPath }} - - name: config - mountPath: /opt/bitnami/redis-sentinel/mounted-etc - - name: sentinel-tmp-conf - mountPath: /opt/bitnami/redis-sentinel/etc/ - {{- end }} -{{- if .Values.metrics.enabled }} - - name: metrics - image: {{ template "redis.metrics.image" . }} - imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} - command: - - /bin/bash - - -c - - | - if [[ -f '/secrets/redis-password' ]]; then - export REDIS_PASSWORD=$(cat /secrets/redis-password) - fi - redis_exporter - args: - {{- range $key, $value := .Values.metrics.extraArgs }} - - --{{ $key }}={{ $value }} - {{- end }} - env: - - name: REDIS_ALIAS - value: {{ template "redis.fullname" . }} - {{- if and .Values.usePassword (not .Values.usePasswordFile) }} - - name: REDIS_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "redis.secretName" . }} - key: {{ template "redis.secretPasswordKey" . }} - {{- end }} - volumeMounts: - {{- if .Values.usePasswordFile }} - - name: redis-password - mountPath: /secrets/ - {{- end }} - ports: - - name: metrics - containerPort: 9121 - resources: -{{ toYaml .Values.metrics.resources | indent 10 }} -{{- end }} - {{- $needsVolumePermissions := and .Values.volumePermissions.enabled (and ( and .Values.master.persistence.enabled (not .Values.persistence.existingClaim) ) .Values.securityContext.enabled) }} - {{- if or $needsVolumePermissions .Values.sysctlImage.enabled }} - initContainers: - {{- if $needsVolumePermissions }} - - name: volume-permissions - image: "{{ template "redis.volumePermissions.image" . }}" - imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} - command: ["/bin/chown", "-R", "{{ .Values.securityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }}", "{{ .Values.master.persistence.path }}"] - securityContext: - runAsUser: 0 - resources: -{{ toYaml .Values.volumePermissions.resources | indent 10 }} - volumeMounts: - - name: redis-data - mountPath: {{ .Values.master.persistence.path }} - subPath: {{ .Values.master.persistence.subPath }} - {{- end }} - {{- if .Values.sysctlImage.enabled }} - - name: init-sysctl - image: {{ template "redis.sysctl.image" . }} - imagePullPolicy: {{ default "" .Values.sysctlImage.pullPolicy | quote }} - resources: -{{ toYaml .Values.sysctlImage.resources | indent 10 }} - {{- if .Values.sysctlImage.mountHostSys }} - volumeMounts: - - name: host-sys - mountPath: /host-sys - {{- end }} - command: -{{ toYaml .Values.sysctlImage.command | indent 10 }} - securityContext: - privileged: true - runAsUser: 0 - {{- end }} - {{- end }} - volumes: - - name: health - configMap: - name: {{ template "redis.fullname" . }}-health - defaultMode: 0755 - {{- if .Values.usePasswordFile }} - - name: redis-password - secret: - secretName: {{ template "redis.secretName" . }} - items: - - key: {{ template "redis.secretPasswordKey" . }} - path: redis-password - {{- end }} - - name: config - configMap: - name: {{ template "redis.fullname" . }} - {{- if not .Values.master.persistence.enabled }} - - name: "redis-data" - emptyDir: {} - {{- else }} - {{- if .Values.persistence.existingClaim }} - - name: "redis-data" - persistentVolumeClaim: - claimName: {{ .Values.persistence.existingClaim }} - {{- end }} - {{- end }} - {{- if .Values.sysctlImage.mountHostSys }} - - name: host-sys - hostPath: - path: /sys - {{- end }} - - name: redis-tmp-conf - emptyDir: {} - {{- if and .Values.cluster.enabled .Values.sentinel.enabled }} - - name: sentinel-tmp-conf - emptyDir: {} - {{- end }} - {{- if and .Values.master.persistence.enabled (not .Values.persistence.existingClaim) }} - volumeClaimTemplates: - - metadata: - name: redis-data - labels: - app: {{ template "redis.name" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} - component: master - spec: - accessModes: - {{- range .Values.master.persistence.accessModes }} - - {{ . | quote }} - {{- end }} - resources: - requests: - storage: {{ .Values.master.persistence.size | quote }} - {{ include "redis.master.storageClass" . }} - {{- end }} - updateStrategy: - type: {{ .Values.master.statefulset.updateStrategy }} - {{- if .Values.master.statefulset.rollingUpdatePartition }} - {{- if (eq "Recreate" .Values.master.statefulset.updateStrategy) }} - rollingUpdate: null - {{- else }} - rollingUpdate: - partition: {{ .Values.master.statefulset.rollingUpdatePartition }} - {{- end }} - {{- end }} diff --git a/task5/APP-helm/charts/redis/templates/redis-master-svc.yaml b/task5/APP-helm/charts/redis/templates/redis-master-svc.yaml deleted file mode 100755 index 7af8755..0000000 --- a/task5/APP-helm/charts/redis/templates/redis-master-svc.yaml +++ /dev/null @@ -1,34 +0,0 @@ -{{- if not .Values.sentinel.enabled }} -apiVersion: v1 -kind: Service -metadata: - name: {{ template "redis.fullname" . }}-master - labels: - app: {{ template "redis.name" . }} - chart: {{ template "redis.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} - {{- if .Values.master.service.labels -}} - {{ toYaml .Values.master.service.labels | nindent 4 }} - {{- end -}} -{{- if .Values.master.service.annotations }} - annotations: {{ toYaml .Values.master.service.annotations | nindent 4 }} -{{- end }} -spec: - type: {{ .Values.master.service.type }} - {{ if eq .Values.master.service.type "LoadBalancer" -}} {{ if .Values.master.service.loadBalancerIP -}} - loadBalancerIP: {{ .Values.master.service.loadBalancerIP }} - {{ end -}} - {{- end -}} - ports: - - name: redis - port: {{ .Values.master.service.port }} - targetPort: redis - {{- if .Values.master.service.nodePort }} - nodePort: {{ .Values.master.service.nodePort }} - {{- end }} - selector: - app: {{ template "redis.name" . }} - release: {{ .Release.Name }} - role: master -{{- end }} diff --git a/task5/APP-helm/charts/redis/templates/redis-role.yaml b/task5/APP-helm/charts/redis/templates/redis-role.yaml deleted file mode 100755 index 2ae9759..0000000 --- a/task5/APP-helm/charts/redis/templates/redis-role.yaml +++ /dev/null @@ -1,13 +0,0 @@ -{{- if and .Values.rbac.create .Values.rbac.role.rules -}} -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ template "redis.fullname" . }} - labels: - app: {{ template "redis.name" . }} - chart: {{ template "redis.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -rules: -{{ toYaml .Values.rbac.role.rules }} -{{- end -}} diff --git a/task5/APP-helm/charts/redis/templates/redis-rolebinding.yaml b/task5/APP-helm/charts/redis/templates/redis-rolebinding.yaml deleted file mode 100755 index c9c0439..0000000 --- a/task5/APP-helm/charts/redis/templates/redis-rolebinding.yaml +++ /dev/null @@ -1,18 +0,0 @@ -{{- if .Values.rbac.create -}} -apiVersion: rbac.authorization.k8s.io/v1beta1 -kind: RoleBinding -metadata: - name: {{ template "redis.fullname" . }} - labels: - app: {{ template "redis.name" . }} - chart: {{ template "redis.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: {{ template "redis.fullname" . }} -subjects: -- kind: ServiceAccount - name: {{ template "redis.serviceAccountName" . }} -{{- end -}} diff --git a/task5/APP-helm/charts/redis/templates/redis-serviceaccount.yaml b/task5/APP-helm/charts/redis/templates/redis-serviceaccount.yaml deleted file mode 100755 index f027176..0000000 --- a/task5/APP-helm/charts/redis/templates/redis-serviceaccount.yaml +++ /dev/null @@ -1,11 +0,0 @@ -{{- if .Values.serviceAccount.create -}} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ template "redis.serviceAccountName" . }} - labels: - app: {{ template "redis.name" . }} - chart: {{ template "redis.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -{{- end -}} diff --git a/task5/APP-helm/charts/redis/templates/redis-slave-statefulset.yaml b/task5/APP-helm/charts/redis/templates/redis-slave-statefulset.yaml deleted file mode 100755 index 557ae1f..0000000 --- a/task5/APP-helm/charts/redis/templates/redis-slave-statefulset.yaml +++ /dev/null @@ -1,426 +0,0 @@ -{{- if .Values.cluster.enabled }} -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: {{ template "redis.fullname" . }}-slave - labels: - app: {{ template "redis.name" . }} - chart: {{ template "redis.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -spec: -{{- if .Values.slave.updateStrategy }} - strategy: -{{ toYaml .Values.slave.updateStrategy | indent 4 }} -{{- end }} - replicas: {{ .Values.cluster.slaveCount }} - serviceName: {{ template "redis.fullname" . }}-headless - selector: - matchLabels: - app: {{ template "redis.name" . }} - release: {{ .Release.Name }} - role: slave - template: - metadata: - labels: - app: {{ template "redis.name" . }} - release: {{ .Release.Name }} - chart: {{ template "redis.chart" . }} - role: slave - {{- if .Values.slave.podLabels }} -{{ toYaml .Values.slave.podLabels | indent 8 }} - {{- end }} - {{- if and .Values.metrics.enabled .Values.metrics.podLabels }} -{{ toYaml .Values.metrics.podLabels | indent 8 }} - {{- end }} - annotations: - checksum/health: {{ include (print $.Template.BasePath "/health-configmap.yaml") . | sha256sum }} - checksum/configmap: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} - checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} - {{- if .Values.slave.podAnnotations }} -{{ toYaml .Values.slave.podAnnotations | indent 8 }} - {{- end }} - {{- if and .Values.metrics.enabled .Values.metrics.podAnnotations }} -{{ toYaml .Values.metrics.podAnnotations | indent 8 }} - {{- end }} - spec: -{{- include "redis.imagePullSecrets" . | indent 6 }} - {{- if .Values.securityContext.enabled }} - securityContext: - fsGroup: {{ .Values.securityContext.fsGroup }} - {{- if .Values.securityContext.sysctls }} - sysctls: -{{ toYaml .Values.securityContext.sysctls | indent 8 }} - {{- end }} - {{- end }} - serviceAccountName: "{{ template "redis.serviceAccountName" . }}" - {{- if .Values.slave.priorityClassName }} - priorityClassName: "{{ .Values.slave.priorityClassName }}" - {{- end }} - {{- if .Values.slave.nodeSelector }} - nodeSelector: -{{ toYaml .Values.slave.nodeSelector | indent 8 }} - {{- end }} - {{- if .Values.slave.tolerations }} - tolerations: -{{ toYaml .Values.slave.tolerations | indent 8 }} - {{- end }} - {{- if .Values.slave.schedulerName }} - schedulerName: "{{ .Values.slave.schedulerName }}" - {{- end }} - {{- with .Values.slave.affinity }} - affinity: -{{ tpl (toYaml .) $ | indent 8 }} - {{- end }} - containers: - - name: {{ template "redis.fullname" . }} - image: {{ template "redis.image" . }} - imagePullPolicy: {{ .Values.image.pullPolicy | quote }} - {{- if .Values.securityContext.enabled }} - securityContext: - runAsUser: {{ .Values.securityContext.runAsUser }} - {{- end }} - command: - - /bin/bash - - -c - - | - {{- if (eq .Values.securityContext.runAsUser 0.0) }} - useradd redis - chown -R redis {{ .Values.slave.persistence.path }} - {{- end }} - if [[ -n $REDIS_PASSWORD_FILE ]]; then - password_aux=`cat ${REDIS_PASSWORD_FILE}` - export REDIS_PASSWORD=$password_aux - fi - if [[ -n $REDIS_MASTER_PASSWORD_FILE ]]; then - password_aux=`cat ${REDIS_MASTER_PASSWORD_FILE}` - export REDIS_MASTER_PASSWORD=$password_aux - fi - if [[ ! -f /opt/bitnami/redis/etc/replica.conf ]];then - cp /opt/bitnami/redis/mounted-etc/replica.conf /opt/bitnami/redis/etc/replica.conf - fi - if [[ ! -f /opt/bitnami/redis/etc/redis.conf ]];then - cp /opt/bitnami/redis/mounted-etc/redis.conf /opt/bitnami/redis/etc/redis.conf - fi - ARGS=("--port" "${REDIS_PORT}") - ARGS+=("--slaveof" "${REDIS_MASTER_HOST}" "${REDIS_MASTER_PORT_NUMBER}") - {{- if .Values.usePassword }} - ARGS+=("--requirepass" "${REDIS_PASSWORD}") - ARGS+=("--masterauth" "${REDIS_MASTER_PASSWORD}") - {{- else }} - ARGS+=("--protected-mode" "no") - {{- end }} - ARGS+=("--include" "/opt/bitnami/redis/etc/redis.conf") - ARGS+=("--include" "/opt/bitnami/redis/etc/replica.conf") - {{- if .Values.slave.extraFlags }} - {{- range .Values.slave.extraFlags }} - ARGS+=({{ . | quote }}) - {{- end }} - {{- end }} - {{- if .Values.slave.command }} - {{ .Values.slave.command }} "${ARGS[@]}" - {{- else }} - redis-server "${ARGS[@]}" - {{- end }} - env: - - name: REDIS_REPLICATION_MODE - value: slave - - name: REDIS_MASTER_HOST - value: {{ template "redis.fullname" . }}-master-0.{{ template "redis.fullname" . }}-headless.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} - - name: REDIS_PORT - value: {{ .Values.redisPort | quote }} - - name: REDIS_MASTER_PORT_NUMBER - value: {{ .Values.redisPort | quote }} - {{- if .Values.usePassword }} - {{- if .Values.usePasswordFile }} - - name: REDIS_PASSWORD_FILE - value: "/opt/bitnami/redis/secrets/redis-password" - - name: REDIS_MASTER_PASSWORD_FILE - value: "/opt/bitnami/redis/secrets/redis-password" - {{- else }} - - name: REDIS_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "redis.secretName" . }} - key: {{ template "redis.secretPasswordKey" . }} - - name: REDIS_MASTER_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "redis.secretName" . }} - key: {{ template "redis.secretPasswordKey" . }} - {{- end }} - {{- else }} - - name: ALLOW_EMPTY_PASSWORD - value: "yes" - {{- end }} - ports: - - name: redis - containerPort: {{ .Values.redisPort }} - {{- if .Values.slave.livenessProbe.enabled }} - livenessProbe: - initialDelaySeconds: {{ .Values.slave.livenessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.slave.livenessProbe.periodSeconds }} - timeoutSeconds: {{ .Values.slave.livenessProbe.timeoutSeconds }} - successThreshold: {{ .Values.slave.livenessProbe.successThreshold }} - failureThreshold: {{ .Values.slave.livenessProbe.failureThreshold}} - exec: - command: - - sh - - -c - {{- if .Values.sentinel.enabled }} - - /health/ping_liveness_local.sh {{ .Values.slave.livenessProbe.timeoutSeconds }} - {{- else }} - - /health/ping_liveness_local_and_master.sh {{ .Values.slave.livenessProbe.timeoutSeconds }} - {{- end }} - {{- end }} - - {{- if .Values.slave.readinessProbe.enabled }} - readinessProbe: - initialDelaySeconds: {{ .Values.slave.readinessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.slave.readinessProbe.periodSeconds }} - timeoutSeconds: {{ .Values.slave.readinessProbe.timeoutSeconds }} - successThreshold: {{ .Values.slave.readinessProbe.successThreshold }} - failureThreshold: {{ .Values.slave.readinessProbe.failureThreshold }} - exec: - command: - - sh - - -c - {{- if .Values.sentinel.enabled }} - - /health/ping_readiness_local.sh {{ .Values.slave.livenessProbe.timeoutSeconds }} - {{- else }} - - /health/ping_readiness_local_and_master.sh {{ .Values.slave.livenessProbe.timeoutSeconds }} - {{- end }} - {{- end }} - resources: -{{ toYaml .Values.slave.resources | indent 10 }} - volumeMounts: - - name: health - mountPath: /health - {{- if .Values.usePasswordFile }} - - name: redis-password - mountPath: /opt/bitnami/redis/secrets/ - {{- end }} - - name: redis-data - mountPath: /data - - name: config - mountPath: /opt/bitnami/redis/mounted-etc - - name: redis-tmp-conf - mountPath: /opt/bitnami/redis/etc - {{- if and .Values.cluster.enabled .Values.sentinel.enabled }} - - name: sentinel - image: "{{ template "sentinel.image" . }}" - imagePullPolicy: {{ .Values.sentinel.image.pullPolicy | quote }} - {{- if .Values.securityContext.enabled }} - securityContext: - runAsUser: {{ .Values.securityContext.runAsUser }} - {{- end }} - command: - - /bin/bash - - -c - - | - if [[ -n $REDIS_PASSWORD_FILE ]]; then - password_aux=`cat ${REDIS_PASSWORD_FILE}` - export REDIS_PASSWORD=$password_aux - fi - if [[ ! -f /opt/bitnami/redis-sentinel/etc/sentinel.conf ]];then - cp /opt/bitnami/redis-sentinel/mounted-etc/sentinel.conf /opt/bitnami/redis-sentinel/etc/sentinel.conf - {{- if .Values.usePassword }} - printf "\nsentinel auth-pass {{ .Values.sentinel.masterSet }} $REDIS_PASSWORD" >> /opt/bitnami/redis-sentinel/etc/sentinel.conf - {{- end }} - fi - - redis-server /opt/bitnami/redis-sentinel/etc/sentinel.conf --sentinel - env: - {{- if .Values.usePassword }} - {{- if .Values.usePasswordFile }} - - name: REDIS_PASSWORD_FILE - value: "/opt/bitnami/redis/secrets/redis-password" - {{- else }} - - name: REDIS_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "redis.secretName" . }} - key: {{ template "redis.secretPasswordKey" . }} - {{- end }} - {{- else }} - - name: ALLOW_EMPTY_PASSWORD - value: "yes" - {{- end }} - - name: REDIS_SENTINEL_PORT - value: {{ .Values.sentinel.port | quote }} - ports: - - name: redis-sentinel - containerPort: {{ .Values.sentinel.port }} - {{- if .Values.sentinel.livenessProbe.enabled }} - livenessProbe: - initialDelaySeconds: {{ .Values.sentinel.livenessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.sentinel.livenessProbe.periodSeconds }} - timeoutSeconds: {{ .Values.sentinel.livenessProbe.timeoutSeconds }} - successThreshold: {{ .Values.sentinel.livenessProbe.successThreshold }} - failureThreshold: {{ .Values.sentinel.livenessProbe.failureThreshold }} - exec: - command: - - sh - - -c - - /health/ping_sentinel.sh {{ .Values.sentinel.livenessProbe.timeoutSeconds }} - {{- end }} - {{- if .Values.sentinel.readinessProbe.enabled}} - readinessProbe: - initialDelaySeconds: {{ .Values.sentinel.readinessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.sentinel.readinessProbe.periodSeconds }} - timeoutSeconds: {{ .Values.sentinel.readinessProbe.timeoutSeconds }} - successThreshold: {{ .Values.sentinel.readinessProbe.successThreshold }} - failureThreshold: {{ .Values.sentinel.readinessProbe.failureThreshold }} - exec: - command: - - sh - - -c - - /health/ping_sentinel.sh {{ .Values.sentinel.livenessProbe.timeoutSeconds }} - {{- end }} - resources: -{{ toYaml .Values.sentinel.resources | indent 10 }} - volumeMounts: - - name: health - mountPath: /health - {{- if .Values.usePasswordFile }} - - name: redis-password - mountPath: /opt/bitnami/redis/secrets/ - {{- end }} - - name: redis-data - mountPath: {{ .Values.master.persistence.path }} - subPath: {{ .Values.master.persistence.subPath }} - - name: config - mountPath: /opt/bitnami/redis-sentinel/mounted-etc - - name: sentinel-tmp-conf - mountPath: /opt/bitnami/redis-sentinel/etc - {{- end }} -{{- if .Values.metrics.enabled }} - - name: metrics - image: {{ template "redis.metrics.image" . }} - imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} - command: - - /bin/bash - - -c - - | - if [[ -f '/secrets/redis-password' ]]; then - export REDIS_PASSWORD=$(cat /secrets/redis-password) - fi - redis_exporter - args: - {{- range $key, $value := .Values.metrics.extraArgs }} - - --{{ $key }}={{ $value }} - {{- end }} - env: - - name: REDIS_ALIAS - value: {{ template "redis.fullname" . }} - {{- if and .Values.usePassword (not .Values.usePasswordFile) }} - - name: REDIS_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "redis.secretName" . }} - key: {{ template "redis.secretPasswordKey" . }} - {{- end }} - volumeMounts: - {{- if .Values.usePasswordFile }} - - name: redis-password - mountPath: /secrets/ - {{- end }} - ports: - - name: metrics - containerPort: 9121 - resources: -{{ toYaml .Values.metrics.resources | indent 10 }} -{{- end }} - {{- $needsVolumePermissions := and .Values.volumePermissions.enabled (and .Values.slave.persistence.enabled .Values.securityContext.enabled) }} - {{- if or $needsVolumePermissions .Values.sysctlImage.enabled }} - initContainers: - {{- if $needsVolumePermissions }} - - name: volume-permissions - image: "{{ template "redis.volumePermissions.image" . }}" - imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} - command: ["/bin/chown", "-R", "{{ .Values.securityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }}", "{{ .Values.slave.persistence.path }}"] - securityContext: - runAsUser: 0 - resources: -{{ toYaml .Values.volumePermissions.resources | indent 10 }} - volumeMounts: - - name: redis-data - mountPath: {{ .Values.slave.persistence.path }} - subPath: {{ .Values.slave.persistence.subPath }} - {{- end }} - {{- if .Values.sysctlImage.enabled }} - - name: init-sysctl - image: {{ template "redis.sysctl.image" . }} - imagePullPolicy: {{ default "" .Values.sysctlImage.pullPolicy | quote }} - resources: -{{ toYaml .Values.sysctlImage.resources | indent 10 }} - {{- if .Values.sysctlImage.mountHostSys }} - volumeMounts: - - name: host-sys - mountPath: /host-sys - {{- end }} - command: -{{ toYaml .Values.sysctlImage.command | indent 10 }} - securityContext: - privileged: true - runAsUser: 0 - {{- end }} - {{- end }} - volumes: - - name: health - configMap: - name: {{ template "redis.fullname" . }}-health - defaultMode: 0755 - {{- if .Values.usePasswordFile }} - - name: redis-password - secret: - secretName: {{ template "redis.secretName" . }} - items: - - key: {{ template "redis.secretPasswordKey" . }} - path: redis-password - {{- end }} - - name: config - configMap: - name: {{ template "redis.fullname" . }} - {{- if .Values.sysctlImage.mountHostSys }} - - name: host-sys - hostPath: - path: /sys - {{- end }} - - name: sentinel-tmp-conf - emptyDir: {} - - name: redis-tmp-conf - emptyDir: {} - {{- if not .Values.slave.persistence.enabled }} - - name: redis-data - emptyDir: {} - {{- else }} - volumeClaimTemplates: - - metadata: - name: redis-data - labels: - app: {{ template "redis.name" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} - component: slave - spec: - accessModes: - {{- range .Values.slave.persistence.accessModes }} - - {{ . | quote }} - {{- end }} - resources: - requests: - storage: {{ .Values.slave.persistence.size | quote }} - {{ include "redis.slave.storageClass" . }} - {{- end }} - updateStrategy: - type: {{ .Values.slave.statefulset.updateStrategy }} - {{- if .Values.slave.statefulset.rollingUpdatePartition }} - {{- if (eq "Recreate" .Values.slave.statefulset.updateStrategy) }} - rollingUpdate: null - {{- else }} - rollingUpdate: - partition: {{ .Values.slave.statefulset.rollingUpdatePartition }} - {{- end }} - {{- end }} -{{- end }} diff --git a/task5/APP-helm/charts/redis/templates/redis-slave-svc.yaml b/task5/APP-helm/charts/redis/templates/redis-slave-svc.yaml deleted file mode 100755 index e6db293..0000000 --- a/task5/APP-helm/charts/redis/templates/redis-slave-svc.yaml +++ /dev/null @@ -1,35 +0,0 @@ -{{- if and .Values.cluster.enabled (not .Values.sentinel.enabled) }} -apiVersion: v1 -kind: Service -metadata: - name: {{ template "redis.fullname" . }}-slave - labels: - app: {{ template "redis.name" . }} - chart: {{ template "redis.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} - {{- if .Values.slave.service.labels -}} - {{ toYaml .Values.slave.service.labels | nindent 4 }} - {{- end -}} -{{- if .Values.slave.service.annotations }} - annotations: -{{ toYaml .Values.slave.service.annotations | indent 4 }} -{{- end }} -spec: - type: {{ .Values.slave.service.type }} - {{ if eq .Values.slave.service.type "LoadBalancer" -}} {{ if .Values.slave.service.loadBalancerIP -}} - loadBalancerIP: {{ .Values.slave.service.loadBalancerIP }} - {{ end -}} - {{- end -}} - ports: - - name: redis - port: {{ .Values.slave.service.port }} - targetPort: redis - {{- if .Values.slave.service.nodePort }} - nodePort: {{ .Values.slave.service.nodePort }} - {{- end }} - selector: - app: {{ template "redis.name" . }} - release: {{ .Release.Name }} - role: slave -{{- end }} diff --git a/task5/APP-helm/charts/redis/templates/redis-with-sentinel-svc.yaml b/task5/APP-helm/charts/redis/templates/redis-with-sentinel-svc.yaml deleted file mode 100755 index 984de21..0000000 --- a/task5/APP-helm/charts/redis/templates/redis-with-sentinel-svc.yaml +++ /dev/null @@ -1,40 +0,0 @@ -{{- if .Values.sentinel.enabled }} -apiVersion: v1 -kind: Service -metadata: - name: {{ template "redis.fullname" . }} - labels: - app: {{ template "redis.name" . }} - chart: {{ template "redis.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} - {{- if .Values.sentinel.service.labels }} - {{ toYaml .Values.sentinel.service.labels | nindent 4 }} - {{- end }} -{{- if .Values.sentinel.service.annotations }} - annotations: -{{ toYaml .Values.sentinel.service.annotations | indent 4 }} -{{- end }} -spec: - type: {{ .Values.sentinel.service.type }} - {{ if eq .Values.sentinel.service.type "LoadBalancer" -}} {{ if .Values.sentinel.service.loadBalancerIP -}} - loadBalancerIP: {{ .Values.sentinel.service.loadBalancerIP }} - {{ end -}} - {{- end -}} - ports: - - name: redis - port: {{ .Values.sentinel.service.redisPort }} - targetPort: redis - {{- if .Values.sentinel.service.redisNodePort }} - nodePort: {{ .Values.sentinel.service.redisNodePort }} - {{- end }} - - name: redis-sentinel - port: {{ .Values.sentinel.service.sentinelPort }} - targetPort: redis-sentinel - {{- if .Values.sentinel.service.sentinelNodePort }} - nodePort: {{ .Values.sentinel.service.sentinelNodePort }} - {{- end }} - selector: - app: {{ template "redis.name" . }} - release: {{ .Release.Name }} -{{- end }} diff --git a/task5/APP-helm/charts/redis/templates/secret.yaml b/task5/APP-helm/charts/redis/templates/secret.yaml deleted file mode 100755 index 36c9ebf..0000000 --- a/task5/APP-helm/charts/redis/templates/secret.yaml +++ /dev/null @@ -1,18 +0,0 @@ -{{- if and .Values.usePassword (not .Values.existingSecret) -}} -apiVersion: v1 -kind: Secret -metadata: - name: {{ template "redis.fullname" . }} - labels: - app: {{ template "redis.name" . }} - chart: {{ template "redis.chart" . }} - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" -type: Opaque -data: - {{- if .Values.password }} - redis-password: {{ .Values.password | b64enc | quote }} - {{- else }} - redis-password: {{ randAlphaNum 10 | b64enc | quote }} - {{- end }} -{{- end -}} diff --git a/task5/APP-helm/charts/redis/values-production.yaml b/task5/APP-helm/charts/redis/values-production.yaml deleted file mode 100755 index 29ee258..0000000 --- a/task5/APP-helm/charts/redis/values-production.yaml +++ /dev/null @@ -1,559 +0,0 @@ -## Global Docker image parameters -## Please, note that this will override the image parameters, including dependencies, configured to use the global value -## Current available global Docker image parameters: imageRegistry and imagePullSecrets -## -# global: -# imageRegistry: myRegistryName -# imagePullSecrets: -# - myRegistryKeySecretName -# storageClass: myStorageClass - -## Bitnami Redis image version -## ref: https://hub.docker.com/r/bitnami/redis/tags/ -## -image: - registry: docker.io - repository: bitnami/redis - ## Bitnami Redis image tag - ## ref: https://github.com/bitnami/bitnami-docker-redis#supported-tags-and-respective-dockerfile-links - ## - tag: 5.0.5-debian-9-r141 - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - -## String to partially override redis.fullname template (will maintain the release name) -## -# nameOverride: - -## String to fully override redis.fullname template -## -# fullnameOverride: - -## Cluster settings -cluster: - enabled: true - slaveCount: 3 - -## Use redis sentinel in the redis pod. This will disable the master and slave services and -## create one redis service with ports to the sentinel and the redis instances -sentinel: - enabled: false - ## Bitnami Redis Sentintel image version - ## ref: https://hub.docker.com/r/bitnami/redis-sentinel/tags/ - ## - image: - registry: docker.io - repository: bitnami/redis-sentinel - ## Bitnami Redis image tag - ## ref: https://github.com/bitnami/bitnami-docker-redis-sentinel#supported-tags-and-respective-dockerfile-links - ## - tag: 5.0.5-debian-9-r134 - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - masterSet: mymaster - initialCheckTimeout: 5 - quorum: 2 - downAfterMilliseconds: 60000 - failoverTimeout: 18000 - parallelSyncs: 1 - port: 26379 - ## Additional Redis configuration for the sentinel nodes - ## ref: https://redis.io/topics/config - ## - configmap: - ## Configure extra options for Redis Sentinel liveness and readiness probes - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) - ## - livenessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 5 - readinessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 5 - ## Redis Sentinel resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - # resources: - # requests: - # memory: 256Mi - # cpu: 100m - ## Redis Sentinel Service properties - service: - ## Redis Sentinel Service type - type: ClusterIP - sentinelPort: 26379 - redisPort: 6379 - - ## Specify the nodePort value for the LoadBalancer and NodePort service types. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - ## - # sentinelNodePort: - # redisNodePort: - - ## Provide any additional annotations which may be required. This can be used to - ## set the LoadBalancer service type to internal only. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer - ## - annotations: {} - labels: {} - loadBalancerIP: - -## Specifies the Kubernetes Cluster's Domain Name. -## -clusterDomain: cluster.local - -networkPolicy: - ## Specifies whether a NetworkPolicy should be created - ## - enabled: true - - ## The Policy model to apply. When set to false, only pods with the correct - ## client label will have network access to the port Redis is listening - ## on. When true, Redis will accept connections from any source - ## (with the correct destination port). - ## - # allowExternal: true - -serviceAccount: - ## Specifies whether a ServiceAccount should be created - ## - create: false - ## The name of the ServiceAccount to use. - ## If not set and create is true, a name is generated using the fullname template - name: - -rbac: - ## Specifies whether RBAC resources should be created - ## - create: false - - role: - ## Rules to create. It follows the role specification - # rules: - # - apiGroups: - # - extensions - # resources: - # - podsecuritypolicies - # verbs: - # - use - # resourceNames: - # - gce.unprivileged - rules: [] - -## Redis pod Security Context -securityContext: - enabled: true - fsGroup: 1001 - runAsUser: 1001 - ## sysctl settings for master and slave pods - ## - ## Uncomment the setting below to increase the net.core.somaxconn value - ## - # sysctls: - # - name: net.core.somaxconn - # value: "10000" - -## Use password authentication -usePassword: true -## Redis password (both master and slave) -## Defaults to a random 10-character alphanumeric string if not set and usePassword is true -## ref: https://github.com/bitnami/bitnami-docker-redis#setting-the-server-password-on-first-run -## -password: -## Use existing secret (ignores previous password) -# existingSecret: -## Password key to be retrieved from Redis secret -## -# existingSecretPasswordKey: - -## Mount secrets as files instead of environment variables -usePasswordFile: false - -## Persist data to a persistent volume (Redis Master) -persistence: {} - ## A manually managed Persistent Volume and Claim - ## Requires persistence.enabled: true - ## If defined, PVC must be created manually before volume will be bound - # existingClaim: - -# Redis port -redisPort: 6379 - -## -## Redis Master parameters -## -master: - ## Redis command arguments - ## - ## Can be used to specify command line arguments, for example: - ## - command: "/run.sh" - ## Additional Redis configuration for the master nodes - ## ref: https://redis.io/topics/config - ## - configmap: - ## Redis additional command line flags - ## - ## Can be used to specify command line flags, for example: - ## - ## extraFlags: - ## - "--maxmemory-policy volatile-ttl" - ## - "--repl-backlog-size 1024mb" - extraFlags: [] - ## Comma-separated list of Redis commands to disable - ## - ## Can be used to disable Redis commands for security reasons. - ## Commands will be completely disabled by renaming each to an empty string. - ## ref: https://redis.io/topics/security#disabling-of-specific-commands - ## - disableCommands: - - FLUSHDB - - FLUSHALL - - ## Redis Master additional pod labels and annotations - ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ - podLabels: {} - podAnnotations: {} - - ## Redis Master resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - # resources: - # requests: - # memory: 256Mi - # cpu: 100m - ## Use an alternate scheduler, e.g. "stork". - ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ - ## - # schedulerName: - - ## Configure extra options for Redis Master liveness and readiness probes - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) - ## - livenessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 5 - readinessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 5 - - ## Redis Master Node selectors and tolerations for pod assignment - ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector - ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#taints-and-tolerations-beta-feature - ## - # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} - # tolerations: [] - ## Redis Master pod/node affinity/anti-affinity - ## - affinity: {} - - ## Redis Master Service properties - service: - ## Redis Master Service type - type: ClusterIP - port: 6379 - - ## Specify the nodePort value for the LoadBalancer and NodePort service types. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - ## - # nodePort: - - ## Provide any additional annotations which may be required. This can be used to - ## set the LoadBalancer service type to internal only. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer - ## - annotations: {} - labels: {} - loadBalancerIP: - - ## Enable persistence using Persistent Volume Claims - ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ - ## - persistence: - enabled: true - ## The path the volume will be mounted at, useful when using different - ## Redis images. - path: /data - ## The subdirectory of the volume to mount to, useful in dev environments - ## and one PV for multiple services. - subPath: "" - ## redis data Persistent Volume Storage Class - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "-" - accessModes: - - ReadWriteOnce - size: 8Gi - - ## Update strategy, can be set to RollingUpdate or onDelete by default. - ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets - statefulset: - updateStrategy: RollingUpdate - ## Partition update strategy - ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions - # rollingUpdatePartition: - - ## Redis Master pod priorityClassName - # priorityClassName: {} - -## -## Redis Slave properties -## Note: service.type is a mandatory parameter -## The rest of the parameters are either optional or, if undefined, will inherit those declared in Redis Master -## -slave: - ## Slave Service properties - service: - ## Redis Slave Service type - type: ClusterIP - ## Redis port - port: 6379 - ## Specify the nodePort value for the LoadBalancer and NodePort service types. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - ## - # nodePort: - - ## Provide any additional annotations which may be required. This can be used to - ## set the LoadBalancer service type to internal only. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer - ## - annotations: {} - labels: {} - loadBalancerIP: - - ## Redis slave port - port: 6379 - ## Can be used to specify command line arguments, for example: - ## - command: "/run.sh" - ## Additional Redis configuration for the slave nodes - ## ref: https://redis.io/topics/config - ## - configmap: - ## Redis extra flags - extraFlags: [] - ## List of Redis commands to disable - disableCommands: - - FLUSHDB - - FLUSHALL - - ## Redis Slave pod/node affinity/anti-affinity - ## - affinity: {} - - ## Configure extra options for Redis Slave liveness and readiness probes - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) - ## - livenessProbe: - enabled: true - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 5 - readinessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 10 - timeoutSeconds: 10 - successThreshold: 1 - failureThreshold: 5 - - ## Redis slave Resource - # resources: - # requests: - # memory: 256Mi - # cpu: 100m - - ## Redis slave selectors and tolerations for pod assignment - # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} - # tolerations: [] - - ## Use an alternate scheduler, e.g. "stork". - ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ - ## - # schedulerName: - - ## Redis slave pod Annotation and Labels - podLabels: {} - podAnnotations: {} - - ## Redis slave pod priorityClassName - # priorityClassName: {} - - ## Enable persistence using Persistent Volume Claims - ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ - ## - persistence: - enabled: true - ## The path the volume will be mounted at, useful when using different - ## Redis images. - path: /data - ## The subdirectory of the volume to mount to, useful in dev environments - ## and one PV for multiple services. - subPath: "" - ## redis data Persistent Volume Storage Class - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "-" - accessModes: - - ReadWriteOnce - size: 8Gi - - ## Update strategy, can be set to RollingUpdate or onDelete by default. - ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets - statefulset: - updateStrategy: RollingUpdate - ## Partition update strategy - ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions - # rollingUpdatePartition: - -## Prometheus Exporter / Metrics -## -metrics: - enabled: true - - image: - registry: docker.io - repository: bitnami/redis-exporter - tag: 1.1.1-debian-9-r13 - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - - ## Metrics exporter resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - ## - # resources: {} - - ## Extra arguments for Metrics exporter, for example: - ## extraArgs: - ## check-keys: myKey,myOtherKey - # extraArgs: {} - - ## Metrics exporter pod priorityClassName - # priorityClassName: {} - service: - type: ClusterIP - ## Use serviceLoadBalancerIP to request a specific static IP, - ## otherwise leave blank - # loadBalancerIP: - annotations: {} - labels: {} - - ## Metrics exporter pod Annotation and Labels - podAnnotations: - prometheus.io/scrape: "true" - prometheus.io/port: "9121" - # podLabels: {} - - # Enable this if you're using https://github.com/coreos/prometheus-operator - serviceMonitor: - enabled: false - ## Specify a namespace if needed - # namespace: monitoring - # fallback to the prometheus default unless specified - # interval: 10s - ## Defaults to what's used if you follow CoreOS [Prometheus Install Instructions](https://github.com/helm/charts/tree/master/stable/prometheus-operator#tldr) - ## [Prometheus Selector Label](https://github.com/helm/charts/tree/master/stable/prometheus-operator#prometheus-operator-1) - ## [Kube Prometheus Selector Label](https://github.com/helm/charts/tree/master/stable/prometheus-operator#exporters) - selector: - prometheus: kube-prometheus - -## -## Init containers parameters: -## volumePermissions: Change the owner of the persist volume mountpoint to RunAsUser:fsGroup -## -volumePermissions: - enabled: false - image: - registry: docker.io - repository: bitnami/minideb - tag: stretch - pullPolicy: Always - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - resources: {} - # resources: - # requests: - # memory: 128Mi - # cpu: 100m - -## Redis config file -## ref: https://redis.io/topics/config -## -configmap: |- - # Enable AOF https://redis.io/topics/persistence#append-only-file - appendonly yes - # Disable RDB persistence, AOF persistence already enabled. - save "" - -## Sysctl InitContainer -## used to perform sysctl operation to modify Kernel settings (needed sometimes to avoid warnings) -sysctlImage: - enabled: false - command: [] - registry: docker.io - repository: bitnami/minideb - tag: stretch - pullPolicy: Always - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - mountHostSys: false - resources: {} - # resources: - # requests: - # memory: 128Mi - # cpu: 100m diff --git a/task5/APP-helm/charts/redis/values.yaml b/task5/APP-helm/charts/redis/values.yaml deleted file mode 100755 index 16e1ac2..0000000 --- a/task5/APP-helm/charts/redis/values.yaml +++ /dev/null @@ -1,558 +0,0 @@ -## Global Docker image parameters -## Please, note that this will override the image parameters, including dependencies, configured to use the global value -## Current available global Docker image parameters: imageRegistry and imagePullSecrets -## -# global: -# imageRegistry: myRegistryName -# imagePullSecrets: -# - myRegistryKeySecretName -# storageClass: myStorageClass - -## Bitnami Redis image version -## ref: https://hub.docker.com/r/bitnami/redis/tags/ -## -image: - registry: docker.io - repository: bitnami/redis - ## Bitnami Redis image tag - ## ref: https://github.com/bitnami/bitnami-docker-redis#supported-tags-and-respective-dockerfile-links - ## - tag: 5.0.5-debian-9-r141 - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - -## String to partially override redis.fullname template (will maintain the release name) -## -# nameOverride: - -## String to fully override redis.fullname template -## -# fullnameOverride: - -## Cluster settings -cluster: - enabled: true - slaveCount: 2 - -## Use redis sentinel in the redis pod. This will disable the master and slave services and -## create one redis service with ports to the sentinel and the redis instances -sentinel: - enabled: false - ## Bitnami Redis Sentintel image version - ## ref: https://hub.docker.com/r/bitnami/redis-sentinel/tags/ - ## - image: - registry: docker.io - repository: bitnami/redis-sentinel - ## Bitnami Redis image tag - ## ref: https://github.com/bitnami/bitnami-docker-redis-sentinel#supported-tags-and-respective-dockerfile-links - ## - tag: 5.0.5-debian-9-r134 - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - masterSet: mymaster - initialCheckTimeout: 5 - quorum: 2 - downAfterMilliseconds: 60000 - failoverTimeout: 18000 - parallelSyncs: 1 - port: 26379 - ## Additional Redis configuration for the sentinel nodes - ## ref: https://redis.io/topics/config - ## - configmap: - ## Configure extra options for Redis Sentinel liveness and readiness probes - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) - ## - livenessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 5 - readinessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 5 - ## Redis Sentinel resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - # resources: - # requests: - # memory: 256Mi - # cpu: 100m - ## Redis Sentinel Service properties - service: - ## Redis Sentinel Service type - type: ClusterIP - sentinelPort: 26379 - redisPort: 6379 - - ## Specify the nodePort value for the LoadBalancer and NodePort service types. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - ## - # sentinelNodePort: - # redisNodePort: - - ## Provide any additional annotations which may be required. This can be used to - ## set the LoadBalancer service type to internal only. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer - ## - annotations: {} - labels: {} - loadBalancerIP: - -## Specifies the Kubernetes Cluster's Domain Name. -## -clusterDomain: cluster.local - -networkPolicy: - ## Specifies whether a NetworkPolicy should be created - ## - enabled: false - - ## The Policy model to apply. When set to false, only pods with the correct - ## client label will have network access to the port Redis is listening - ## on. When true, Redis will accept connections from any source - ## (with the correct destination port). - ## - # allowExternal: true - -serviceAccount: - ## Specifies whether a ServiceAccount should be created - ## - create: false - ## The name of the ServiceAccount to use. - ## If not set and create is true, a name is generated using the fullname template - name: - -rbac: - ## Specifies whether RBAC resources should be created - ## - create: false - - role: - ## Rules to create. It follows the role specification - # rules: - # - apiGroups: - # - extensions - # resources: - # - podsecuritypolicies - # verbs: - # - use - # resourceNames: - # - gce.unprivileged - rules: [] - -## Redis pod Security Context -securityContext: - enabled: true - fsGroup: 1001 - runAsUser: 1001 - ## sysctl settings for master and slave pods - ## - ## Uncomment the setting below to increase the net.core.somaxconn value - ## - # sysctls: - # - name: net.core.somaxconn - # value: "10000" - -## Use password authentication -usePassword: true -## Redis password (both master and slave) -## Defaults to a random 10-character alphanumeric string if not set and usePassword is true -## ref: https://github.com/bitnami/bitnami-docker-redis#setting-the-server-password-on-first-run -## -password: -## Use existing secret (ignores previous password) -# existingSecret: -## Password key to be retrieved from Redis secret -## -# existingSecretPasswordKey: - -## Mount secrets as files instead of environment variables -usePasswordFile: false - -## Persist data to a persistent volume (Redis Master) -persistence: {} - ## A manually managed Persistent Volume and Claim - ## Requires persistence.enabled: true - ## If defined, PVC must be created manually before volume will be bound - # existingClaim: - -# Redis port -redisPort: 6379 - -## -## Redis Master parameters -## -master: - ## Redis command arguments - ## - ## Can be used to specify command line arguments, for example: - ## - command: "/run.sh" - ## Additional Redis configuration for the master nodes - ## ref: https://redis.io/topics/config - ## - configmap: - ## Redis additional command line flags - ## - ## Can be used to specify command line flags, for example: - ## - ## extraFlags: - ## - "--maxmemory-policy volatile-ttl" - ## - "--repl-backlog-size 1024mb" - extraFlags: [] - ## Comma-separated list of Redis commands to disable - ## - ## Can be used to disable Redis commands for security reasons. - ## Commands will be completely disabled by renaming each to an empty string. - ## ref: https://redis.io/topics/security#disabling-of-specific-commands - ## - disableCommands: - - FLUSHDB - - FLUSHALL - - ## Redis Master additional pod labels and annotations - ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ - podLabels: {} - podAnnotations: {} - - ## Redis Master resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - # resources: - # requests: - # memory: 256Mi - # cpu: 100m - ## Use an alternate scheduler, e.g. "stork". - ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ - ## - # schedulerName: - - ## Configure extra options for Redis Master liveness and readiness probes - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) - ## - livenessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 5 - readinessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 5 - - ## Redis Master Node selectors and tolerations for pod assignment - ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector - ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#taints-and-tolerations-beta-feature - ## - # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} - # tolerations: [] - ## Redis Master pod/node affinity/anti-affinity - ## - affinity: {} - - ## Redis Master Service properties - service: - ## Redis Master Service type - type: ClusterIP - port: 6379 - - ## Specify the nodePort value for the LoadBalancer and NodePort service types. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - ## - # nodePort: - - ## Provide any additional annotations which may be required. This can be used to - ## set the LoadBalancer service type to internal only. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer - ## - annotations: {} - labels: {} - loadBalancerIP: - - ## Enable persistence using Persistent Volume Claims - ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ - ## - persistence: - enabled: true - ## The path the volume will be mounted at, useful when using different - ## Redis images. - path: /var/log - ## The subdirectory of the volume to mount to, useful in dev environments - ## and one PV for multiple services. - subPath: "" - ## redis data Persistent Volume Storage Class - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "-" - accessModes: - - ReadWriteOnce - size: 8Gi - - ## Update strategy, can be set to RollingUpdate or onDelete by default. - ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets - statefulset: - updateStrategy: RollingUpdate - ## Partition update strategy - ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions - # rollingUpdatePartition: - - ## Redis Master pod priorityClassName - # priorityClassName: {} - -## -## Redis Slave properties -## Note: service.type is a mandatory parameter -## The rest of the parameters are either optional or, if undefined, will inherit those declared in Redis Master -## -slave: - ## Slave Service properties - service: - ## Redis Slave Service type - type: ClusterIP - ## Redis port - port: 6379 - ## Specify the nodePort value for the LoadBalancer and NodePort service types. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - ## - # nodePort: - - ## Provide any additional annotations which may be required. This can be used to - ## set the LoadBalancer service type to internal only. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer - ## - annotations: {} - labels: {} - loadBalancerIP: - - ## Redis slave port - port: 6379 - ## Can be used to specify command line arguments, for example: - ## - command: "/run.sh" - ## Additional Redis configuration for the slave nodes - ## ref: https://redis.io/topics/config - ## - configmap: - ## Redis extra flags - extraFlags: [] - ## List of Redis commands to disable - disableCommands: - - FLUSHDB - - FLUSHALL - - ## Redis Slave pod/node affinity/anti-affinity - ## - affinity: {} - - ## Configure extra options for Redis Slave liveness and readiness probes - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) - ## - livenessProbe: - enabled: true - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 5 - readinessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 10 - timeoutSeconds: 10 - successThreshold: 1 - failureThreshold: 5 - - ## Redis slave Resource - # resources: - # requests: - # memory: 256Mi - # cpu: 100m - - ## Redis slave selectors and tolerations for pod assignment - # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} - # tolerations: [] - - ## Use an alternate scheduler, e.g. "stork". - ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ - ## - # schedulerName: - - ## Redis slave pod Annotation and Labels - podLabels: {} - podAnnotations: {} - - ## Redis slave pod priorityClassName - # priorityClassName: {} - - ## Enable persistence using Persistent Volume Claims - ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ - ## - persistence: - enabled: true - ## The path the volume will be mounted at, useful when using different - ## Redis images. - path: /data - ## The subdirectory of the volume to mount to, useful in dev environments - ## and one PV for multiple services. - subPath: "" - ## redis data Persistent Volume Storage Class - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "-" - accessModes: - - ReadWriteOnce - size: 8Gi - - ## Update strategy, can be set to RollingUpdate or onDelete by default. - ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets - statefulset: - updateStrategy: RollingUpdate - ## Partition update strategy - ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions - # rollingUpdatePartition: - -## Prometheus Exporter / Metrics -## -metrics: - enabled: false - - image: - registry: docker.io - repository: bitnami/redis-exporter - tag: 1.1.1-debian-9-r13 - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - ## Metrics exporter resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - ## - # resources: {} - - ## Extra arguments for Metrics exporter, for example: - ## extraArgs: - ## check-keys: myKey,myOtherKey - # extraArgs: {} - - ## Metrics exporter pod Annotation and Labels - podAnnotations: - prometheus.io/scrape: "true" - prometheus.io/port: "9121" - # podLabels: {} - - # Enable this if you're using https://github.com/coreos/prometheus-operator - serviceMonitor: - enabled: false - ## Specify a namespace if needed - # namespace: monitoring - # fallback to the prometheus default unless specified - # interval: 10s - ## Defaults to what's used if you follow CoreOS [Prometheus Install Instructions](https://github.com/helm/charts/tree/master/stable/prometheus-operator#tldr) - ## [Prometheus Selector Label](https://github.com/helm/charts/tree/master/stable/prometheus-operator#prometheus-operator-1) - ## [Kube Prometheus Selector Label](https://github.com/helm/charts/tree/master/stable/prometheus-operator#exporters) - selector: - prometheus: kube-prometheus - - ## Metrics exporter pod priorityClassName - # priorityClassName: {} - service: - type: ClusterIP - ## Use serviceLoadBalancerIP to request a specific static IP, - ## otherwise leave blank - # loadBalancerIP: - annotations: {} - labels: {} - -## -## Init containers parameters: -## volumePermissions: Change the owner of the persist volume mountpoint to RunAsUser:fsGroup -## -volumePermissions: - enabled: false - image: - registry: docker.io - repository: bitnami/minideb - tag: stretch - pullPolicy: Always - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - resources: {} - # resources: - # requests: - # memory: 128Mi - # cpu: 100m - -## Redis config file -## ref: https://redis.io/topics/config -## -configmap: |- - # Enable AOF https://redis.io/topics/persistence#append-only-file - appendonly yes - # Disable RDB persistence, AOF persistence already enabled. - save "" - -## Sysctl InitContainer -## used to perform sysctl operation to modify Kernel settings (needed sometimes to avoid warnings) -sysctlImage: - enabled: false - command: [] - registry: docker.io - repository: bitnami/minideb - tag: stretch - pullPolicy: Always - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - mountHostSys: false - resources: {} - # resources: - # requests: - # memory: 128Mi - # cpu: 100m diff --git a/task5/APP-helm/deploy.sh b/task5/APP-helm/deploy.sh old mode 100644 new mode 100755 index c936506..e96fdff --- a/task5/APP-helm/deploy.sh +++ b/task5/APP-helm/deploy.sh @@ -1,14 +1,10 @@ #!/bin/bash - -TILLER-NAMESPACE="" -NAMESPACE="" - helm upgrade \ --install \ --wait \ --force \ --timeout 900 \ - --tiller-namespace=$TILLER-NAMESPACE \ - --namespace=$NAMESPACE \ + --tiller-namespace=kryptonite\ + --namespace=testingmy \ my-app \ . diff --git a/task5/APP-helm/requirements.lock b/task5/APP-helm/requirements.lock new file mode 100644 index 0000000..b0e1092 --- /dev/null +++ b/task5/APP-helm/requirements.lock @@ -0,0 +1,6 @@ +dependencies: +- name: mongodb + repository: https://kubernetes-charts.storage.googleapis.com/ + version: 4.8.4 +digest: sha256:ed7a8a27dac61e511d23f7419832faaf426717d3777a0dd82c86aa5a12f6c912 +generated: "2020-02-29T23:43:30.809633206+02:00" diff --git a/task5/APP-helm/requirements.yaml b/task5/APP-helm/requirements.yaml index 947917f..7d18c6c 100644 --- a/task5/APP-helm/requirements.yaml +++ b/task5/APP-helm/requirements.yaml @@ -1,4 +1,5 @@ dependencies: - - name: redis - version: 9.3.2 + - name: mongodb + alias: usersmongodb + version: 4.8.4 repository: https://kubernetes-charts.storage.googleapis.com/ \ No newline at end of file diff --git a/task5/APP-helm/templates/configmap.yaml b/task5/APP-helm/templates/configmap.yaml index 7436f93..0442610 100644 --- a/task5/APP-helm/templates/configmap.yaml +++ b/task5/APP-helm/templates/configmap.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: {{ tpl (.Values.app.env.config.name ) . }} - namespace: .Values.app.env.config.namespace + namespace: testingmy labels: chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} release: {{ .Release.Name }} diff --git a/task5/APP-helm/templates/deployment.yaml b/task5/APP-helm/templates/deployment.yaml index a94494c..71c0711 100644 --- a/task5/APP-helm/templates/deployment.yaml +++ b/task5/APP-helm/templates/deployment.yaml @@ -47,9 +47,16 @@ spec: timeoutSeconds: 5 failureThreshold: 5 resources: - limits: - cpu: 1000m - memory: 1500Mi - requests: - cpu: 50m - memory: 256Mi \ No newline at end of file +{{ toYaml .Values.resources | indent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} diff --git a/task5/APP-helm/values.yaml b/task5/APP-helm/values.yaml index 6cf5042..913c350 100644 --- a/task5/APP-helm/values.yaml +++ b/task5/APP-helm/values.yaml @@ -1,4 +1,4 @@ -replicaCount: 3 +replicaCount: 1 image: repository: saifnuaimii/app @@ -11,14 +11,23 @@ service: internalPort: 8080 externalPort: 80 -redis: - master: - persistence: - enabled: true - livenessProbe: - enabled: false - readinessProbe: - enabled: false +usersmongodb: + mongodbUsername: root + mongodbPassword: root + mongodbDatabase: users + persistence: + enabled: false + livenessProbe: + enabled: false + readinessProbe: + enabled: false + resources: + limits: + cpu: 500m + memory: 756Mi + requests: + cpu: 50m + memory: 256Mi ingress: enabled: false @@ -33,11 +42,18 @@ app: config: source: app.properties name: configmap-{{ .Values.app.env.config.source }} - namespace: "" + resources: limits: cpu: 1000m memory: 1500Mi requests: cpu: 50m - memory: 256Mi \ No newline at end of file + memory: 256Mi + + +nodeSelector: {} + +tolerations: [] + +affinity: {} \ No newline at end of file From b7fa44bb4e0820a993a17e573b42e42d0f1bdc63 Mon Sep 17 00:00:00 2001 From: Saif Al Nuaimi Date: Sun, 1 Mar 2020 20:40:00 +0200 Subject: [PATCH 07/18] feat: commit --- APP/docker-compose.yml | 1 + ...application.properties => application-dev.properties} | 0 task1/APP/docker-compose.yml | 1 + ...application.properties => application-dev.properties} | 0 task3/APP/docker-compose.yml | 1 + ...application.properties => application-dev.properties} | 0 task3/start.sh | 4 ++++ task5/APP-helm/app.properties | 9 ++++----- task5/APP-helm/values.yaml | 2 +- task5/{APP-helm => }/deploy.sh | 5 +++-- 10 files changed, 15 insertions(+), 8 deletions(-) rename APP/src/main/resources/{application.properties => application-dev.properties} (100%) rename task1/APP/src/main/resources/{application.properties => application-dev.properties} (100%) rename task3/APP/src/main/resources/{application.properties => application-dev.properties} (100%) create mode 100644 task3/start.sh rename task5/{APP-helm => }/deploy.sh (63%) diff --git a/APP/docker-compose.yml b/APP/docker-compose.yml index 38a7ec1..c205ef7 100644 --- a/APP/docker-compose.yml +++ b/APP/docker-compose.yml @@ -9,6 +9,7 @@ services: dockerfile: local.dockerfile environment: - SPRING_DATA_MONGODB_URI=mongodb://root:root@mongo-db:27017/ + - SPRING_DATA_MONGODB_DATABASE=users ports: - 8080:8080 depends_on: diff --git a/APP/src/main/resources/application.properties b/APP/src/main/resources/application-dev.properties similarity index 100% rename from APP/src/main/resources/application.properties rename to APP/src/main/resources/application-dev.properties diff --git a/task1/APP/docker-compose.yml b/task1/APP/docker-compose.yml index 38a7ec1..c205ef7 100644 --- a/task1/APP/docker-compose.yml +++ b/task1/APP/docker-compose.yml @@ -9,6 +9,7 @@ services: dockerfile: local.dockerfile environment: - SPRING_DATA_MONGODB_URI=mongodb://root:root@mongo-db:27017/ + - SPRING_DATA_MONGODB_DATABASE=users ports: - 8080:8080 depends_on: diff --git a/task1/APP/src/main/resources/application.properties b/task1/APP/src/main/resources/application-dev.properties similarity index 100% rename from task1/APP/src/main/resources/application.properties rename to task1/APP/src/main/resources/application-dev.properties diff --git a/task3/APP/docker-compose.yml b/task3/APP/docker-compose.yml index 38a7ec1..c205ef7 100644 --- a/task3/APP/docker-compose.yml +++ b/task3/APP/docker-compose.yml @@ -9,6 +9,7 @@ services: dockerfile: local.dockerfile environment: - SPRING_DATA_MONGODB_URI=mongodb://root:root@mongo-db:27017/ + - SPRING_DATA_MONGODB_DATABASE=users ports: - 8080:8080 depends_on: diff --git a/task3/APP/src/main/resources/application.properties b/task3/APP/src/main/resources/application-dev.properties similarity index 100% rename from task3/APP/src/main/resources/application.properties rename to task3/APP/src/main/resources/application-dev.properties diff --git a/task3/start.sh b/task3/start.sh new file mode 100644 index 0000000..61394ae --- /dev/null +++ b/task3/start.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +cd APP +docker-compose up + diff --git a/task5/APP-helm/app.properties b/task5/APP-helm/app.properties index 67f95dd..b0f138d 100644 --- a/task5/APP-helm/app.properties +++ b/task5/APP-helm/app.properties @@ -1,5 +1,4 @@ -spring.data.mongodb.uri: "mongodb://root:root@my-app-usersmongodb:27017/?ssl=false&authSource=admin&authMechanism=SCRAM-SHA-1" -spring.data.mongodb.database: "users" -spring.data.mongodb.username: "root" -spring.data.mongodb.password: "root" - +spring.data.mongodb.host: {{ .Release.Name }}-usersmongodb:27017 +spring.data.mongodb.database: {{ .Values.usersmongodb.mongodbDatabase }} +spring.data.mongodb.username: {{ .Values.usersmongodb.mongodbUsername }} +spring.data.mongodb.password: {{ .Values.usersmongodb.mongodbPassword }} \ No newline at end of file diff --git a/task5/APP-helm/values.yaml b/task5/APP-helm/values.yaml index 913c350..adff6b7 100644 --- a/task5/APP-helm/values.yaml +++ b/task5/APP-helm/values.yaml @@ -1,4 +1,4 @@ -replicaCount: 1 +replicaCount: 3 image: repository: saifnuaimii/app diff --git a/task5/APP-helm/deploy.sh b/task5/deploy.sh similarity index 63% rename from task5/APP-helm/deploy.sh rename to task5/deploy.sh index e96fdff..fcdca6c 100755 --- a/task5/APP-helm/deploy.sh +++ b/task5/deploy.sh @@ -1,10 +1,11 @@ #!/bin/bash +cd APP-helm helm upgrade \ --install \ --wait \ --force \ --timeout 900 \ - --tiller-namespace=kryptonite\ - --namespace=testingmy \ + --tiller-namespace=""\ + --namespace="" \ my-app \ . From 2e4061a9578f2a1e632a0144950ebe992a1e5184 Mon Sep 17 00:00:00 2001 From: Saif Al Nuaimi Date: Sun, 1 Mar 2020 20:47:31 +0200 Subject: [PATCH 08/18] feat: commit --- APP/local.dockerfile | 2 -- docs/steps.txt | 6 ++++++ task1/APP/local.dockerfile | 2 -- task3/APP/local.dockerfile | 3 --- task5/APP-helm/templates/configmap.yaml | 2 +- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/APP/local.dockerfile b/APP/local.dockerfile index d8e2d56..ea13072 100644 --- a/APP/local.dockerfile +++ b/APP/local.dockerfile @@ -1,6 +1,5 @@ FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 EXPOSE 8080 -RUN apk add redis WORKDIR /myapp COPY src /myapp/src COPY pom.xml /myapp/pom.xml @@ -11,6 +10,5 @@ WORKDIR /myrunningapp ARG hostname=saifnuaimii/app:v1.0.0 EXPOSE 8080 COPY --from=mvn_builder_stage1 /myapp . -CMD redis-cli -h redis-server -p 8080 CMD java -D -jar target/app-*.jar \ No newline at end of file diff --git a/docs/steps.txt b/docs/steps.txt index 36eecce..b7b3c8c 100644 --- a/docs/steps.txt +++ b/docs/steps.txt @@ -47,5 +47,11 @@ ________________ Task 5: Start build my helm chart using basic files values.yaml requirements.yaml Chart.yaml +I create templates and add configmap.yaml, deployment.yaml, service.yaml finally i get a +dependency chart from chart repo. +configure the app.properties to match the database. + +Kindly add your tiller-namespace line 8 and namespace line 9 to task5/deploy.sh +and the namespace to task5/APP-helm/templates/configmap.yaml line 5 diff --git a/task1/APP/local.dockerfile b/task1/APP/local.dockerfile index d8e2d56..ea13072 100644 --- a/task1/APP/local.dockerfile +++ b/task1/APP/local.dockerfile @@ -1,6 +1,5 @@ FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 EXPOSE 8080 -RUN apk add redis WORKDIR /myapp COPY src /myapp/src COPY pom.xml /myapp/pom.xml @@ -11,6 +10,5 @@ WORKDIR /myrunningapp ARG hostname=saifnuaimii/app:v1.0.0 EXPOSE 8080 COPY --from=mvn_builder_stage1 /myapp . -CMD redis-cli -h redis-server -p 8080 CMD java -D -jar target/app-*.jar \ No newline at end of file diff --git a/task3/APP/local.dockerfile b/task3/APP/local.dockerfile index d8e2d56..22a3e37 100644 --- a/task3/APP/local.dockerfile +++ b/task3/APP/local.dockerfile @@ -1,6 +1,5 @@ FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 EXPOSE 8080 -RUN apk add redis WORKDIR /myapp COPY src /myapp/src COPY pom.xml /myapp/pom.xml @@ -11,6 +10,4 @@ WORKDIR /myrunningapp ARG hostname=saifnuaimii/app:v1.0.0 EXPOSE 8080 COPY --from=mvn_builder_stage1 /myapp . -CMD redis-cli -h redis-server -p 8080 - CMD java -D -jar target/app-*.jar \ No newline at end of file diff --git a/task5/APP-helm/templates/configmap.yaml b/task5/APP-helm/templates/configmap.yaml index 0442610..7737502 100644 --- a/task5/APP-helm/templates/configmap.yaml +++ b/task5/APP-helm/templates/configmap.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: {{ tpl (.Values.app.env.config.name ) . }} - namespace: testingmy + namespace: "" labels: chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} release: {{ .Release.Name }} From f5b4793b3ea3693587ecc946bd80fdad07f7d3a6 Mon Sep 17 00:00:00 2001 From: Saif Al Nuaimi Date: Mon, 2 Mar 2020 00:03:22 +0200 Subject: [PATCH 09/18] feat: commit --- task2/devops-task | 1 + task2/scripts/builder.sh | 2 +- task4/APP/.gitignore | 31 ++ .../.mvn/wrapper/MavenWrapperDownloader.java | 118 +++++++ task4/APP/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes .../APP/.mvn/wrapper/maven-wrapper.properties | 2 + task4/APP/Dockerfile | 13 + task4/APP/ansible.yaml | 5 + task4/APP/docker-compose.yml | 27 ++ task4/APP/local.dockerfile | 14 + task4/APP/mvnw | 310 ++++++++++++++++++ task4/APP/mvnw.cmd | 182 ++++++++++ task4/APP/pom.xml | 59 ++++ .../java/com/webapp/app/AppApplication.java | 13 + .../app/controller/CounterController.java | 82 +++++ .../main/java/com/webapp/app/model/User.java | 44 +++ .../app/repository/MongoRepositoryImpl.java | 12 + .../main/resources/application-dev.properties | 2 + task4/APP/src/main/resources/static/main.js | 138 ++++++++ .../src/main/resources/templates/index.html | 43 +++ 20 files changed, 1097 insertions(+), 1 deletion(-) create mode 160000 task2/devops-task create mode 100644 task4/APP/.gitignore create mode 100644 task4/APP/.mvn/wrapper/MavenWrapperDownloader.java create mode 100644 task4/APP/.mvn/wrapper/maven-wrapper.jar create mode 100644 task4/APP/.mvn/wrapper/maven-wrapper.properties create mode 100644 task4/APP/Dockerfile create mode 100644 task4/APP/ansible.yaml create mode 100644 task4/APP/docker-compose.yml create mode 100644 task4/APP/local.dockerfile create mode 100644 task4/APP/mvnw create mode 100644 task4/APP/mvnw.cmd create mode 100644 task4/APP/pom.xml create mode 100644 task4/APP/src/main/java/com/webapp/app/AppApplication.java create mode 100644 task4/APP/src/main/java/com/webapp/app/controller/CounterController.java create mode 100644 task4/APP/src/main/java/com/webapp/app/model/User.java create mode 100755 task4/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java create mode 100644 task4/APP/src/main/resources/application-dev.properties create mode 100755 task4/APP/src/main/resources/static/main.js create mode 100755 task4/APP/src/main/resources/templates/index.html diff --git a/task2/devops-task b/task2/devops-task new file mode 160000 index 0000000..2e4061a --- /dev/null +++ b/task2/devops-task @@ -0,0 +1 @@ +Subproject commit 2e4061a9578f2a1e632a0144950ebe992a1e5184 diff --git a/task2/scripts/builder.sh b/task2/scripts/builder.sh index 96fd5ed..fcb1180 100755 --- a/task2/scripts/builder.sh +++ b/task2/scripts/builder.sh @@ -2,7 +2,7 @@ set e GIT_REPO_URL="https://github.com/saifal-nuaimi/devops-task.git" DOCKER_UNAME="saifnuaimii" -DOCKER_UPASS="S9494@@if" +DOCKER_UPASS="P@ssw0rdd" DOCKER_IMAGE=$DOCKER_UNAME/app DOCKER_IMAGE_VERSION="v1.0.0" diff --git a/task4/APP/.gitignore b/task4/APP/.gitignore new file mode 100644 index 0000000..a2a3040 --- /dev/null +++ b/task4/APP/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/task4/APP/.mvn/wrapper/MavenWrapperDownloader.java b/task4/APP/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..a45eb6b --- /dev/null +++ b/task4/APP/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/task4/APP/.mvn/wrapper/maven-wrapper.jar b/task4/APP/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 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 EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/task4/APP/.mvn/wrapper/maven-wrapper.properties b/task4/APP/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..642d572 --- /dev/null +++ b/task4/APP/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/task4/APP/Dockerfile b/task4/APP/Dockerfile new file mode 100644 index 0000000..22a3e37 --- /dev/null +++ b/task4/APP/Dockerfile @@ -0,0 +1,13 @@ +FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 +EXPOSE 8080 +WORKDIR /myapp +COPY src /myapp/src +COPY pom.xml /myapp/pom.xml +RUN mvn clean install + +FROM openjdk:8-jre-alpine as mvn_runner_stage2 +WORKDIR /myrunningapp +ARG hostname=saifnuaimii/app:v1.0.0 +EXPOSE 8080 +COPY --from=mvn_builder_stage1 /myapp . +CMD java -D -jar target/app-*.jar \ No newline at end of file diff --git a/task4/APP/ansible.yaml b/task4/APP/ansible.yaml new file mode 100644 index 0000000..39546bc --- /dev/null +++ b/task4/APP/ansible.yaml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + tasks: + - name: maven clean install + shell: mvn clean install diff --git a/task4/APP/docker-compose.yml b/task4/APP/docker-compose.yml new file mode 100644 index 0000000..c205ef7 --- /dev/null +++ b/task4/APP/docker-compose.yml @@ -0,0 +1,27 @@ +version: '2' + +services: + + #-----------------web app----------------- + webapp: + build: + context: . + dockerfile: local.dockerfile + environment: + - SPRING_DATA_MONGODB_URI=mongodb://root:root@mongo-db:27017/ + - SPRING_DATA_MONGODB_DATABASE=users + ports: + - 8080:8080 + depends_on: + - mongo-db + + #-----------------mongo db----------------- + mongo-db: + restart: always + image: mongo + ports: + - "27017:27017" + environment: + - MONGO_INITDB_ROOT_USERNAME=root + - MONGO_INITDB_ROOT_PASSWORD=root + - MONGO_INITDB_DATABASE=users \ No newline at end of file diff --git a/task4/APP/local.dockerfile b/task4/APP/local.dockerfile new file mode 100644 index 0000000..ea13072 --- /dev/null +++ b/task4/APP/local.dockerfile @@ -0,0 +1,14 @@ +FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 +EXPOSE 8080 +WORKDIR /myapp +COPY src /myapp/src +COPY pom.xml /myapp/pom.xml +RUN mvn clean install + +FROM openjdk:8-jre-alpine as mvn_runner_stage2 +WORKDIR /myrunningapp +ARG hostname=saifnuaimii/app:v1.0.0 +EXPOSE 8080 +COPY --from=mvn_builder_stage1 /myapp . + +CMD java -D -jar target/app-*.jar \ No newline at end of file diff --git a/task4/APP/mvnw b/task4/APP/mvnw new file mode 100644 index 0000000..a16b543 --- /dev/null +++ b/task4/APP/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/task4/APP/mvnw.cmd b/task4/APP/mvnw.cmd new file mode 100644 index 0000000..c8d4337 --- /dev/null +++ b/task4/APP/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/task4/APP/pom.xml b/task4/APP/pom.xml new file mode 100644 index 0000000..1029d36 --- /dev/null +++ b/task4/APP/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.0.4.RELEASE + + + com.webapp + app + 1.0.0-RELEASE + APP + Demo project for Spring Boot + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/task4/APP/src/main/java/com/webapp/app/AppApplication.java b/task4/APP/src/main/java/com/webapp/app/AppApplication.java new file mode 100644 index 0000000..1b16909 --- /dev/null +++ b/task4/APP/src/main/java/com/webapp/app/AppApplication.java @@ -0,0 +1,13 @@ +package com.webapp.app; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class AppApplication { + + public static void main(String[] args) { + SpringApplication.run(AppApplication.class, args); + } + +} diff --git a/task4/APP/src/main/java/com/webapp/app/controller/CounterController.java b/task4/APP/src/main/java/com/webapp/app/controller/CounterController.java new file mode 100644 index 0000000..8beebdf --- /dev/null +++ b/task4/APP/src/main/java/com/webapp/app/controller/CounterController.java @@ -0,0 +1,82 @@ +package com.webapp.app.controller; + +import com.webapp.app.model.User; +import com.webapp.app.repository.MongoRepositoryImpl; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Optional; + + +@RestController +public class CounterController extends HttpServlet { + + private final MongoRepositoryImpl mongoRepository; + + private int iHitCounter; + + public CounterController(MongoRepositoryImpl mongoRepository) { + this.mongoRepository = mongoRepository; + iHitCounter = 0; + } + + @GetMapping("/") + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { + String userAgent = request.getHeader("User-Agent"); + + String remoteAddr = request.getHeader("X-FORWARDED-FOR"); + + if (StringUtils.isEmpty(remoteAddr)) { + remoteAddr = request.getRemoteAddr(); + } + + User user = getUpdatedUserInfo(remoteAddr); + + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + + printStartHTMLTag(out); + out.println("
"); + out.println("Hello World !"); + out.print("
"); + out.println("

Welcome to my website !

"); + out.print("
"); + out.println("You are visitor number: " + (++iHitCounter)); + out.print("
"); + out.println("IP number: " + remoteAddr); + out.print("
"); + out.println("User Agent: " + userAgent); + out.print("
"); + out.println("count : " + user.getCount()); + out.println("
"); + printEndHTMLTag(out); + } + + private User getUpdatedUserInfo(String remoteAddr) { + Optional current = mongoRepository.findById(remoteAddr); + User user = new User(remoteAddr, 1); + current.ifPresent(u -> user.setCount(u.getCount() + 1)); + mongoRepository.save(user); + return user; + } + + private void printEndHTMLTag(PrintWriter out) { + out.println(""); + out.println(""); + } + + private void printStartHTMLTag(PrintWriter out) { + out.println(""); + out.println(""); + out.println("Hello World"); + out.println(""); + out.println(""); + } + +} \ No newline at end of file diff --git a/task4/APP/src/main/java/com/webapp/app/model/User.java b/task4/APP/src/main/java/com/webapp/app/model/User.java new file mode 100644 index 0000000..80ee670 --- /dev/null +++ b/task4/APP/src/main/java/com/webapp/app/model/User.java @@ -0,0 +1,44 @@ +package com.webapp.app.model; + +import org.springframework.data.annotation.Id; + +import java.io.Serializable; + + +public class User implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + private String id; + private int count; + + public User(String id, int count) { + this.id = id; + this.count = count; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + @Override + public String toString() { + return "User{" + + "id='" + id + '\'' + + ", count=" + count + + '}'; + } +} diff --git a/task4/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java b/task4/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java new file mode 100755 index 0000000..3d49eef --- /dev/null +++ b/task4/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java @@ -0,0 +1,12 @@ +package com.webapp.app.repository; + + +import com.webapp.app.model.User; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface MongoRepositoryImpl extends MongoRepository { + + +} diff --git a/task4/APP/src/main/resources/application-dev.properties b/task4/APP/src/main/resources/application-dev.properties new file mode 100644 index 0000000..a01bd52 --- /dev/null +++ b/task4/APP/src/main/resources/application-dev.properties @@ -0,0 +1,2 @@ +spring.data.mongodb.uri=mongodb://root:root@localhost:27017/ +spring.data.mongodb.database=users \ No newline at end of file diff --git a/task4/APP/src/main/resources/static/main.js b/task4/APP/src/main/resources/static/main.js new file mode 100755 index 0000000..08c6332 --- /dev/null +++ b/task4/APP/src/main/resources/static/main.js @@ -0,0 +1,138 @@ +/** + * Create HTML table row. + * + * \param text (str) HTML code to be placed inside the row. + */ +function tr(text) { + return '' + text + ''; +} + +/** + * Create HTML table cell element. + * + * \param text (str) The text to be placed inside the cell. + */ +function td(text) { + return '' + text + ''; +} + +/** + * Edit value: load key and value into inputs. + */ +function editButton(key) { + var format = ''; + return format.replace(/{key}/g, key); +} + +/** + * Create HTML button with which a key-value pair is deleted from persistence. + * + * \param key (str) that'll be deleted when the created button is clicked. + */ +function deleteButton(key) { + var format = '' + return format.replace(/{key}/g, key); +} + +/** + * Create HTML table row element. + * + * \param key (str) text into the key cell. + * \param value (str) text into the value cell. + */ +function row(key, value) { + return $( + tr( + td(key) + + td(value) + + td(editButton(key)) + + td(deleteButton(key)))); +} + +/** + * Clear and reload the values in data table. + */ +function refreshTable() { + $.get('/values', function(data) { + var attr, + mainTable = $('#mainTable tbody'); + mainTable.empty(); + for (attr in data) { + if (data.hasOwnProperty(attr)) { + mainTable.append(row(attr, data[attr])); + } + } + }); +} + +function editKey(key) { + /* Find the row with key in first column (key column). */ + var format = '#mainTable tbody td:first-child:contains("{key}")', + selector = format.replace(/{key}/, key), + cells = $(selector).parent().children(), + key = cells[0].textContent, + value = cells[1].textContent, + keyInput = $('#keyInput'), + valueInput = $('#valueInput'); + + /* Load the key and value texts into inputs + * Select value text so it can be directly typed to + */ + keyInput.val(key); + valueInput.val(value); + valueInput.select(); +} + +/** + * Delete key-value pair. + * + * \param key (str) The key to be deleted. + */ +function deleteKey(key) { + /* + * Delete the key. + * Reload keys and values in table to reflect the deleted ones. + * Set keyboard focus to key input: ready to start typing. + */ + $.post('/delete', {key: key}, function() { + refreshTable(); + $('#keyInput').focus(); + }); +} + +$(document).ready(function() { + var keyInput = $('#keyInput'), + valueInput = $('#valueInput'); + + refreshTable(); + $('#addForm').on('submit', function(event) { + var data = { + key: keyInput.val(), + value: valueInput.val() + }; + + /* + * Persist the new key-value pair. + * Clear the inputs. + * Set keyboard focus to key input: ready to start typing. + */ + $.post('/add', data, function() { + refreshTable(); + keyInput.val(''); + valueInput.val(''); + keyInput.focus(); + }); + /* Prevent HTTP form submit */ + event.preventDefault(); + }); + + /* Focus keyboard input into key input; ready to start typing */ + keyInput.focus(); +}); diff --git a/task4/APP/src/main/resources/templates/index.html b/task4/APP/src/main/resources/templates/index.html new file mode 100755 index 0000000..9fdef13 --- /dev/null +++ b/task4/APP/src/main/resources/templates/index.html @@ -0,0 +1,43 @@ + + + +Intro to Redis with Spring Boot + + + + +
+
+
+
+
+ + +
+
+ + +
+ +
+
+
+ + + + + + + + + + +
KeyValue
+
+
+
+ + + + + From 6813e95f559ed1ca207d73d0faa3df53aa3851c7 Mon Sep 17 00:00:00 2001 From: Saif Al Nuaimi Date: Mon, 2 Mar 2020 00:40:44 +0200 Subject: [PATCH 10/18] feat: commit --- docs/steps.txt | 1 + task2/devops-task | 1 - task4/APP/ansible.yaml | 7 +++++-- task4/APP/build.sh | 2 ++ task4/APP/git-repo-save.sh | 1 + task4/APP/relase.txt | 1 + 6 files changed, 10 insertions(+), 3 deletions(-) delete mode 160000 task2/devops-task create mode 100644 task4/APP/build.sh create mode 100644 task4/APP/git-repo-save.sh create mode 100644 task4/APP/relase.txt diff --git a/docs/steps.txt b/docs/steps.txt index b7b3c8c..da06b4a 100644 --- a/docs/steps.txt +++ b/docs/steps.txt @@ -41,6 +41,7 @@ ________________ Task 4: +Start by reading more about ansible and create basic playbook that ask mvn to deploy all decadency by script and stop there. ________________ diff --git a/task2/devops-task b/task2/devops-task deleted file mode 160000 index 2e4061a..0000000 --- a/task2/devops-task +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2e4061a9578f2a1e632a0144950ebe992a1e5184 diff --git a/task4/APP/ansible.yaml b/task4/APP/ansible.yaml index 39546bc..dda68dc 100644 --- a/task4/APP/ansible.yaml +++ b/task4/APP/ansible.yaml @@ -1,5 +1,8 @@ --- - hosts: localhost tasks: - - name: maven clean install - shell: mvn clean install + - name: mvn build + command: sh build.sh + - name: git clone repo + command: sh git-repo-save.sh + diff --git a/task4/APP/build.sh b/task4/APP/build.sh new file mode 100644 index 0000000..120873c --- /dev/null +++ b/task4/APP/build.sh @@ -0,0 +1,2 @@ +apt-get install mvn +mvn clean install \ No newline at end of file diff --git a/task4/APP/git-repo-save.sh b/task4/APP/git-repo-save.sh new file mode 100644 index 0000000..0f49f59 --- /dev/null +++ b/task4/APP/git-repo-save.sh @@ -0,0 +1 @@ +git clone https://github.com/saifal-nuaimi/devops-task.git /opt/my_app/release_version \ No newline at end of file diff --git a/task4/APP/relase.txt b/task4/APP/relase.txt new file mode 100644 index 0000000..0ec25f7 --- /dev/null +++ b/task4/APP/relase.txt @@ -0,0 +1 @@ +v1.0.0 From b43b19eda62605863d3c2a631a77da87c3f26f05 Mon Sep 17 00:00:00 2001 From: Saif Al Nuaimi Date: Mon, 2 Mar 2020 01:00:47 +0200 Subject: [PATCH 11/18] feat: commit --- docs/Tech Skills.odt | Bin 17593 -> 17619 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/Tech Skills.odt b/docs/Tech Skills.odt index 87e8fc393f859927690d13c198a014fa51df55f6..fbb10ed9e7e7b9797d15f4a99b3b31cabbbbb901 100644 GIT binary patch delta 9486 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 A_5c6? delta 9418 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 Date: Tue, 3 Mar 2020 00:11:27 +0200 Subject: [PATCH 12/18] fix: fix notes --- APP/.gitignore | 31 -- APP/.mvn/wrapper/MavenWrapperDownloader.java | 118 ------- APP/.mvn/wrapper/maven-wrapper.jar | Bin 50710 -> 0 bytes APP/.mvn/wrapper/maven-wrapper.properties | 2 - APP/Dockerfile | 13 - APP/docker-compose.yml | 27 -- APP/local.dockerfile | 14 - APP/mvnw | 310 ------------------ APP/mvnw.cmd | 182 ---------- APP/pom.xml | 59 ---- .../java/com/webapp/app/AppApplication.java | 13 - .../app/controller/CounterController.java | 82 ----- .../main/java/com/webapp/app/model/User.java | 44 --- .../app/repository/MongoRepositoryImpl.java | 12 - .../main/resources/application-dev.properties | 2 - APP/src/main/resources/static/main.js | 138 -------- APP/src/main/resources/templates/index.html | 43 --- docs/steps.txt | 26 ++ task2/scripts/builder.sh | 80 +++-- task3/APP/.gitignore | 31 -- .../.mvn/wrapper/MavenWrapperDownloader.java | 118 ------- task3/APP/.mvn/wrapper/maven-wrapper.jar | Bin 50710 -> 0 bytes .../APP/.mvn/wrapper/maven-wrapper.properties | 2 - task3/APP/Dockerfile | 13 - task3/APP/local.dockerfile | 13 - task3/APP/mvnw | 310 ------------------ task3/APP/mvnw.cmd | 182 ---------- task3/APP/pom.xml | 59 ---- .../java/com/webapp/app/AppApplication.java | 13 - .../app/controller/CounterController.java | 82 ----- .../main/java/com/webapp/app/model/User.java | 44 --- .../app/repository/MongoRepositoryImpl.java | 12 - .../main/resources/application-dev.properties | 2 - task3/APP/src/main/resources/static/main.js | 138 -------- .../src/main/resources/templates/index.html | 43 --- task3/{APP => }/docker-compose.yml | 4 +- task3/start.sh | 1 - task4/APP/.gitignore | 31 -- .../.mvn/wrapper/MavenWrapperDownloader.java | 118 ------- task4/APP/.mvn/wrapper/maven-wrapper.jar | Bin 50710 -> 0 bytes .../APP/.mvn/wrapper/maven-wrapper.properties | 2 - task4/APP/Dockerfile | 13 - task4/APP/ansible.yaml | 8 - task4/APP/build.sh | 2 - task4/APP/docker-compose.yml | 27 -- task4/APP/git-repo-save.sh | 1 - task4/APP/local.dockerfile | 14 - task4/APP/mvnw | 310 ------------------ task4/APP/mvnw.cmd | 182 ---------- task4/APP/pom.xml | 59 ---- task4/APP/relase.txt | 1 - .../java/com/webapp/app/AppApplication.java | 13 - .../app/controller/CounterController.java | 82 ----- .../main/java/com/webapp/app/model/User.java | 44 --- .../app/repository/MongoRepositoryImpl.java | 12 - .../main/resources/application-dev.properties | 2 - task4/APP/src/main/resources/static/main.js | 138 -------- .../src/main/resources/templates/index.html | 43 --- task5/deploy.sh | 6 +- task5/{APP-helm => helm}/Chart.yaml | 0 task5/{APP-helm => helm}/app.properties | 0 .../charts/mongodb-4.8.4.tgz | Bin task5/{APP-helm => helm}/requirements.lock | 0 task5/{APP-helm => helm}/requirements.yaml | 0 .../templates/configmap.yaml | 0 .../templates/deployment.yaml | 0 .../{APP-helm => helm}/templates/service.yaml | 0 task5/{APP-helm => helm}/values.yaml | 0 68 files changed, 87 insertions(+), 3284 deletions(-) delete mode 100644 APP/.gitignore delete mode 100644 APP/.mvn/wrapper/MavenWrapperDownloader.java delete mode 100644 APP/.mvn/wrapper/maven-wrapper.jar delete mode 100644 APP/.mvn/wrapper/maven-wrapper.properties delete mode 100644 APP/Dockerfile delete mode 100644 APP/docker-compose.yml delete mode 100644 APP/local.dockerfile delete mode 100644 APP/mvnw delete mode 100644 APP/mvnw.cmd delete mode 100644 APP/pom.xml delete mode 100644 APP/src/main/java/com/webapp/app/AppApplication.java delete mode 100644 APP/src/main/java/com/webapp/app/controller/CounterController.java delete mode 100644 APP/src/main/java/com/webapp/app/model/User.java delete mode 100755 APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java delete mode 100644 APP/src/main/resources/application-dev.properties delete mode 100755 APP/src/main/resources/static/main.js delete mode 100755 APP/src/main/resources/templates/index.html delete mode 100644 task3/APP/.gitignore delete mode 100644 task3/APP/.mvn/wrapper/MavenWrapperDownloader.java delete mode 100644 task3/APP/.mvn/wrapper/maven-wrapper.jar delete mode 100644 task3/APP/.mvn/wrapper/maven-wrapper.properties delete mode 100644 task3/APP/Dockerfile delete mode 100644 task3/APP/local.dockerfile delete mode 100644 task3/APP/mvnw delete mode 100644 task3/APP/mvnw.cmd delete mode 100644 task3/APP/pom.xml delete mode 100644 task3/APP/src/main/java/com/webapp/app/AppApplication.java delete mode 100644 task3/APP/src/main/java/com/webapp/app/controller/CounterController.java delete mode 100644 task3/APP/src/main/java/com/webapp/app/model/User.java delete mode 100755 task3/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java delete mode 100644 task3/APP/src/main/resources/application-dev.properties delete mode 100755 task3/APP/src/main/resources/static/main.js delete mode 100755 task3/APP/src/main/resources/templates/index.html rename task3/{APP => }/docker-compose.yml (89%) delete mode 100644 task4/APP/.gitignore delete mode 100644 task4/APP/.mvn/wrapper/MavenWrapperDownloader.java delete mode 100644 task4/APP/.mvn/wrapper/maven-wrapper.jar delete mode 100644 task4/APP/.mvn/wrapper/maven-wrapper.properties delete mode 100644 task4/APP/Dockerfile delete mode 100644 task4/APP/ansible.yaml delete mode 100644 task4/APP/build.sh delete mode 100644 task4/APP/docker-compose.yml delete mode 100644 task4/APP/git-repo-save.sh delete mode 100644 task4/APP/local.dockerfile delete mode 100644 task4/APP/mvnw delete mode 100644 task4/APP/mvnw.cmd delete mode 100644 task4/APP/pom.xml delete mode 100644 task4/APP/relase.txt delete mode 100644 task4/APP/src/main/java/com/webapp/app/AppApplication.java delete mode 100644 task4/APP/src/main/java/com/webapp/app/controller/CounterController.java delete mode 100644 task4/APP/src/main/java/com/webapp/app/model/User.java delete mode 100755 task4/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java delete mode 100644 task4/APP/src/main/resources/application-dev.properties delete mode 100755 task4/APP/src/main/resources/static/main.js delete mode 100755 task4/APP/src/main/resources/templates/index.html rename task5/{APP-helm => helm}/Chart.yaml (100%) rename task5/{APP-helm => helm}/app.properties (100%) rename task5/{APP-helm => helm}/charts/mongodb-4.8.4.tgz (100%) rename task5/{APP-helm => helm}/requirements.lock (100%) rename task5/{APP-helm => helm}/requirements.yaml (100%) rename task5/{APP-helm => helm}/templates/configmap.yaml (100%) rename task5/{APP-helm => helm}/templates/deployment.yaml (100%) rename task5/{APP-helm => helm}/templates/service.yaml (100%) rename task5/{APP-helm => helm}/values.yaml (100%) diff --git a/APP/.gitignore b/APP/.gitignore deleted file mode 100644 index a2a3040..0000000 --- a/APP/.gitignore +++ /dev/null @@ -1,31 +0,0 @@ -HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/** -!**/src/test/** - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ - -### VS Code ### -.vscode/ diff --git a/APP/.mvn/wrapper/MavenWrapperDownloader.java b/APP/.mvn/wrapper/MavenWrapperDownloader.java deleted file mode 100644 index a45eb6b..0000000 --- a/APP/.mvn/wrapper/MavenWrapperDownloader.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.net.*; -import java.io.*; -import java.nio.channels.*; -import java.util.Properties; - -public class MavenWrapperDownloader { - - private static final String WRAPPER_VERSION = "0.5.6"; - /** - * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. - */ - private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" - + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; - - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to - * use instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; - - /** - * Path where the maven-wrapper.jar will be saved to. - */ - private static final String MAVEN_WRAPPER_JAR_PATH = - ".mvn/wrapper/maven-wrapper.jar"; - - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; - - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); - - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if (mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); - } catch (IOException e) { - System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if (mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream.close(); - } - } catch (IOException e) { - // Ignore ... - } - } - } - System.out.println("- Downloading from: " + url); - - File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if (!outputFile.getParentFile().exists()) { - if (!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); - } - } - System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); - try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); - System.exit(1); - } - } - - private static void downloadFileFromURL(String urlString, File destination) throws Exception { - if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { - String username = System.getenv("MVNW_USERNAME"); - char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); - Authenticator.setDefault(new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(username, password); - } - }); - } - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(destination); - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); - } - -} diff --git a/APP/.mvn/wrapper/maven-wrapper.jar b/APP/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index 2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50710 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 EfAhh=DF6Tf diff --git a/APP/.mvn/wrapper/maven-wrapper.properties b/APP/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index 642d572..0000000 --- a/APP/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,2 +0,0 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/APP/Dockerfile b/APP/Dockerfile deleted file mode 100644 index 22a3e37..0000000 --- a/APP/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 -EXPOSE 8080 -WORKDIR /myapp -COPY src /myapp/src -COPY pom.xml /myapp/pom.xml -RUN mvn clean install - -FROM openjdk:8-jre-alpine as mvn_runner_stage2 -WORKDIR /myrunningapp -ARG hostname=saifnuaimii/app:v1.0.0 -EXPOSE 8080 -COPY --from=mvn_builder_stage1 /myapp . -CMD java -D -jar target/app-*.jar \ No newline at end of file diff --git a/APP/docker-compose.yml b/APP/docker-compose.yml deleted file mode 100644 index c205ef7..0000000 --- a/APP/docker-compose.yml +++ /dev/null @@ -1,27 +0,0 @@ -version: '2' - -services: - - #-----------------web app----------------- - webapp: - build: - context: . - dockerfile: local.dockerfile - environment: - - SPRING_DATA_MONGODB_URI=mongodb://root:root@mongo-db:27017/ - - SPRING_DATA_MONGODB_DATABASE=users - ports: - - 8080:8080 - depends_on: - - mongo-db - - #-----------------mongo db----------------- - mongo-db: - restart: always - image: mongo - ports: - - "27017:27017" - environment: - - MONGO_INITDB_ROOT_USERNAME=root - - MONGO_INITDB_ROOT_PASSWORD=root - - MONGO_INITDB_DATABASE=users \ No newline at end of file diff --git a/APP/local.dockerfile b/APP/local.dockerfile deleted file mode 100644 index ea13072..0000000 --- a/APP/local.dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 -EXPOSE 8080 -WORKDIR /myapp -COPY src /myapp/src -COPY pom.xml /myapp/pom.xml -RUN mvn clean install - -FROM openjdk:8-jre-alpine as mvn_runner_stage2 -WORKDIR /myrunningapp -ARG hostname=saifnuaimii/app:v1.0.0 -EXPOSE 8080 -COPY --from=mvn_builder_stage1 /myapp . - -CMD java -D -jar target/app-*.jar \ No newline at end of file diff --git a/APP/mvnw b/APP/mvnw deleted file mode 100644 index a16b543..0000000 --- a/APP/mvnw +++ /dev/null @@ -1,310 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Maven Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` - fi - # end of workaround - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -BASE_DIR=`find_maven_basedir "$(pwd)"` -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi -else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi - if [ -n "$MVNW_REPOURL" ]; then - jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - else - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; - esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" - if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` - fi - - if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget "$jarUrl" -O "$wrapperJarPath" - else - wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl -o "$wrapperJarPath" "$jarUrl" -f - else - curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f - fi - - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaClass=`cygpath --path --windows "$javaClass"` - fi - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") - fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR -fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/APP/mvnw.cmd b/APP/mvnw.cmd deleted file mode 100644 index c8d4337..0000000 --- a/APP/mvnw.cmd +++ /dev/null @@ -1,182 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM https://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - -FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) -) -@REM End of extension - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% diff --git a/APP/pom.xml b/APP/pom.xml deleted file mode 100644 index 1029d36..0000000 --- a/APP/pom.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.0.4.RELEASE - - - com.webapp - app - 1.0.0-RELEASE - APP - Demo project for Spring Boot - - - 1.8 - - - - - org.springframework.boot - spring-boot-starter-actuator - - - - org.springframework.boot - spring-boot-starter-data-mongodb - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/APP/src/main/java/com/webapp/app/AppApplication.java b/APP/src/main/java/com/webapp/app/AppApplication.java deleted file mode 100644 index 1b16909..0000000 --- a/APP/src/main/java/com/webapp/app/AppApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.webapp.app; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class AppApplication { - - public static void main(String[] args) { - SpringApplication.run(AppApplication.class, args); - } - -} diff --git a/APP/src/main/java/com/webapp/app/controller/CounterController.java b/APP/src/main/java/com/webapp/app/controller/CounterController.java deleted file mode 100644 index 8beebdf..0000000 --- a/APP/src/main/java/com/webapp/app/controller/CounterController.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.webapp.app.controller; - -import com.webapp.app.model.User; -import com.webapp.app.repository.MongoRepositoryImpl; -import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Optional; - - -@RestController -public class CounterController extends HttpServlet { - - private final MongoRepositoryImpl mongoRepository; - - private int iHitCounter; - - public CounterController(MongoRepositoryImpl mongoRepository) { - this.mongoRepository = mongoRepository; - iHitCounter = 0; - } - - @GetMapping("/") - public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { - String userAgent = request.getHeader("User-Agent"); - - String remoteAddr = request.getHeader("X-FORWARDED-FOR"); - - if (StringUtils.isEmpty(remoteAddr)) { - remoteAddr = request.getRemoteAddr(); - } - - User user = getUpdatedUserInfo(remoteAddr); - - response.setContentType("text/html"); - PrintWriter out = response.getWriter(); - - printStartHTMLTag(out); - out.println("
"); - out.println("Hello World !"); - out.print("
"); - out.println("

Welcome to my website !

"); - out.print("
"); - out.println("You are visitor number: " + (++iHitCounter)); - out.print("
"); - out.println("IP number: " + remoteAddr); - out.print("
"); - out.println("User Agent: " + userAgent); - out.print("
"); - out.println("count : " + user.getCount()); - out.println("
"); - printEndHTMLTag(out); - } - - private User getUpdatedUserInfo(String remoteAddr) { - Optional current = mongoRepository.findById(remoteAddr); - User user = new User(remoteAddr, 1); - current.ifPresent(u -> user.setCount(u.getCount() + 1)); - mongoRepository.save(user); - return user; - } - - private void printEndHTMLTag(PrintWriter out) { - out.println(""); - out.println(""); - } - - private void printStartHTMLTag(PrintWriter out) { - out.println(""); - out.println(""); - out.println("Hello World"); - out.println(""); - out.println(""); - } - -} \ No newline at end of file diff --git a/APP/src/main/java/com/webapp/app/model/User.java b/APP/src/main/java/com/webapp/app/model/User.java deleted file mode 100644 index 80ee670..0000000 --- a/APP/src/main/java/com/webapp/app/model/User.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.webapp.app.model; - -import org.springframework.data.annotation.Id; - -import java.io.Serializable; - - -public class User implements Serializable { - - private static final long serialVersionUID = 1L; - - @Id - private String id; - private int count; - - public User(String id, int count) { - this.id = id; - this.count = count; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public int getCount() { - return count; - } - - public void setCount(int count) { - this.count = count; - } - - @Override - public String toString() { - return "User{" + - "id='" + id + '\'' + - ", count=" + count + - '}'; - } -} diff --git a/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java b/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java deleted file mode 100755 index 3d49eef..0000000 --- a/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.webapp.app.repository; - - -import com.webapp.app.model.User; -import org.springframework.data.mongodb.repository.MongoRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface MongoRepositoryImpl extends MongoRepository { - - -} diff --git a/APP/src/main/resources/application-dev.properties b/APP/src/main/resources/application-dev.properties deleted file mode 100644 index a01bd52..0000000 --- a/APP/src/main/resources/application-dev.properties +++ /dev/null @@ -1,2 +0,0 @@ -spring.data.mongodb.uri=mongodb://root:root@localhost:27017/ -spring.data.mongodb.database=users \ No newline at end of file diff --git a/APP/src/main/resources/static/main.js b/APP/src/main/resources/static/main.js deleted file mode 100755 index 08c6332..0000000 --- a/APP/src/main/resources/static/main.js +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Create HTML table row. - * - * \param text (str) HTML code to be placed inside the row. - */ -function tr(text) { - return '' + text + ''; -} - -/** - * Create HTML table cell element. - * - * \param text (str) The text to be placed inside the cell. - */ -function td(text) { - return '' + text + ''; -} - -/** - * Edit value: load key and value into inputs. - */ -function editButton(key) { - var format = ''; - return format.replace(/{key}/g, key); -} - -/** - * Create HTML button with which a key-value pair is deleted from persistence. - * - * \param key (str) that'll be deleted when the created button is clicked. - */ -function deleteButton(key) { - var format = '' - return format.replace(/{key}/g, key); -} - -/** - * Create HTML table row element. - * - * \param key (str) text into the key cell. - * \param value (str) text into the value cell. - */ -function row(key, value) { - return $( - tr( - td(key) + - td(value) + - td(editButton(key)) + - td(deleteButton(key)))); -} - -/** - * Clear and reload the values in data table. - */ -function refreshTable() { - $.get('/values', function(data) { - var attr, - mainTable = $('#mainTable tbody'); - mainTable.empty(); - for (attr in data) { - if (data.hasOwnProperty(attr)) { - mainTable.append(row(attr, data[attr])); - } - } - }); -} - -function editKey(key) { - /* Find the row with key in first column (key column). */ - var format = '#mainTable tbody td:first-child:contains("{key}")', - selector = format.replace(/{key}/, key), - cells = $(selector).parent().children(), - key = cells[0].textContent, - value = cells[1].textContent, - keyInput = $('#keyInput'), - valueInput = $('#valueInput'); - - /* Load the key and value texts into inputs - * Select value text so it can be directly typed to - */ - keyInput.val(key); - valueInput.val(value); - valueInput.select(); -} - -/** - * Delete key-value pair. - * - * \param key (str) The key to be deleted. - */ -function deleteKey(key) { - /* - * Delete the key. - * Reload keys and values in table to reflect the deleted ones. - * Set keyboard focus to key input: ready to start typing. - */ - $.post('/delete', {key: key}, function() { - refreshTable(); - $('#keyInput').focus(); - }); -} - -$(document).ready(function() { - var keyInput = $('#keyInput'), - valueInput = $('#valueInput'); - - refreshTable(); - $('#addForm').on('submit', function(event) { - var data = { - key: keyInput.val(), - value: valueInput.val() - }; - - /* - * Persist the new key-value pair. - * Clear the inputs. - * Set keyboard focus to key input: ready to start typing. - */ - $.post('/add', data, function() { - refreshTable(); - keyInput.val(''); - valueInput.val(''); - keyInput.focus(); - }); - /* Prevent HTTP form submit */ - event.preventDefault(); - }); - - /* Focus keyboard input into key input; ready to start typing */ - keyInput.focus(); -}); diff --git a/APP/src/main/resources/templates/index.html b/APP/src/main/resources/templates/index.html deleted file mode 100755 index 9fdef13..0000000 --- a/APP/src/main/resources/templates/index.html +++ /dev/null @@ -1,43 +0,0 @@ - - - -Intro to Redis with Spring Boot - - - - -
-
-
-
-
- - -
-
- - -
- -
-
-
- - - - - - - - - - -
KeyValue
-
-
-
- - - - - diff --git a/docs/steps.txt b/docs/steps.txt index da06b4a..132da4c 100644 --- a/docs/steps.txt +++ b/docs/steps.txt @@ -37,6 +37,14 @@ Task 3: Build docker-compose from two parts first part is the database part and the second one is the app part. Add all the configuration that the app need on the resources file. + +To start up the docker-compose run ./task3/start.sh or the following command: + +$ git clone https://github.com/saifal-nuaimi/devops-task.git APP +$ cd APP +$ docker-compose up + + ________________ Task 4: @@ -56,3 +64,21 @@ configure the app.properties to match the database. Kindly add your tiller-namespace line 8 and namespace line 9 to task5/deploy.sh and the namespace to task5/APP-helm/templates/configmap.yaml line 5 +To start up the helm chart run ./task5/deploy.sh or the following command : + +cd helm +helm upgrade \ + --install \ + --wait \ + --force \ + --timeout 900 \ + --tiller-namespace=kryptonite\ + --namespace=testingmy \ + my-app \ + . + + + + + + diff --git a/task2/scripts/builder.sh b/task2/scripts/builder.sh index fcb1180..8feac83 100755 --- a/task2/scripts/builder.sh +++ b/task2/scripts/builder.sh @@ -1,15 +1,25 @@ #!/usr/bin/env bash set e +#Variables +#---------------------------------------------------------- GIT_REPO_URL="https://github.com/saifal-nuaimi/devops-task.git" DOCKER_UNAME="saifnuaimii" DOCKER_UPASS="P@ssw0rdd" DOCKER_IMAGE=$DOCKER_UNAME/app DOCKER_IMAGE_VERSION="v1.0.0" -git clone $GIT_REPO_URL +#---------------------------------------------------------- +# cleaning +echo "cleaning ..." +rm -rf devops-task #---------------------------------------------------------- +echo "clone > $GIT_REPO_URL ..." +git clone $GIT_REPO_URL +#---------------------------------------------------------- + +# Check if tag exisit function docker_tag_exists() { TOKEN=$(curl -s -H "Content-Type: application/json" -X POST -d '{"username": "'${DOCKER_UNAME}'", "password": "'${DOCKER_UPASS}'"}' https://hub.docker.com/v2/users/login/ | jq -r .token) EXISTS=$(curl -s -H "Authorization: JWT ${TOKEN}" https://hub.docker.com/v2/repositories/$DOCKER_IMAGE/tags/\?page_size\=10000 | jq -r "[.results | .[] | .name == \"$DOCKER_IMAGE_VERSION\"] | any") @@ -18,42 +28,65 @@ function docker_tag_exists() { if docker_tag_exists $DOCKER_IMAGE $DOCKER_IMAGE_VERSION; then echo $DOCKER_IMAGE $DOCKER_IMAGE_VERSION exist - echo "3" else - echo "2" echo "missing/wrong version.Kindly check your docker repo or fix image name and tag" - exit 1 + exit 2 fi #---------------------------------------------------------- -cd devops-task/APP -pwd +# get right path. +cd ../.. +cd task1/APP +#---------------------------------------------------------- + + +# Building Docker Image function build_docker_image() { docker build -t ${DOCKER_IMAGE}:${DOCKER_IMAGE_VERSION} -f Dockerfile . } -if build_docker_image; then +if docker_tag_exists; then echo "Building your image" build_docker_image else - echo "4" - exit 1 + echo "missing/wrong .Building failed" + exit 4 fi +#------------------------------------------------------ +# run docker-compose to test the service +docker-compose up -d -MY_DOCKER_RANDOM_NAME=myapp$RANDOM -docker run -d --rm --name $MY_DOCKER_RANDOM_NAME ${DOCKER_IMAGE}:${DOCKER_IMAGE_VERSION} -MY_DOCKER_ID=$(docker ps -aqf "name=$MY_DOCKER_RANDOM_NAME") -CONTAINER_STATUS=$(docker inspect -f {{.State.Running}} $MY_DOCKER_ID) +# check if the docker container running +is_healthy() { + service="webapp" + container_id="$(docker-compose ps -q "$service")" + health_status="$(docker inspect -f "{{.State.Status}}" "$container_id")" + echo $health_status + while [ "$health_status" = "running" ] + do + echo "container is running" + sleep 10 + break + done + } + is_healthy +#------------------------------------------------------------------------ +# use the health actuator to check if the service working well -if [ $CONTAINER_STATUS = "true" ]; then +APP_STATUS=$(curl --silent http://localhost:8080/actuator/health | jq .status) +echo $APP_STATUS +if [ $APP_STATUS="UP" ]; then echo "Your application working fine" - echo "5" -else - echo "missing/wrong .Kindly check your docker continer" - exit 1 +else [$APP_STATUS="Down"] + echo "missing/wrong .Application failed in testing" + exit 5 fi -docker stop $MY_DOCKER_RANDOM_NAME - +#----------------------------------------------------- +# Stop the docker-compose +docker-compose down +#----------------------------------------------------- +# +## Push the docker Image docker_push_image() { docker push $DOCKER_IMAGE } @@ -62,6 +95,7 @@ if docker_push_image; then echo "Pushing your image" docker_push_image else - echo "6" - exit 1 -fi \ No newline at end of file + echo "missing/wrong .Pusing failed." + exit 6 +fi +##----------------------------------------------------- \ No newline at end of file diff --git a/task3/APP/.gitignore b/task3/APP/.gitignore deleted file mode 100644 index a2a3040..0000000 --- a/task3/APP/.gitignore +++ /dev/null @@ -1,31 +0,0 @@ -HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/** -!**/src/test/** - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ - -### VS Code ### -.vscode/ diff --git a/task3/APP/.mvn/wrapper/MavenWrapperDownloader.java b/task3/APP/.mvn/wrapper/MavenWrapperDownloader.java deleted file mode 100644 index a45eb6b..0000000 --- a/task3/APP/.mvn/wrapper/MavenWrapperDownloader.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.net.*; -import java.io.*; -import java.nio.channels.*; -import java.util.Properties; - -public class MavenWrapperDownloader { - - private static final String WRAPPER_VERSION = "0.5.6"; - /** - * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. - */ - private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" - + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; - - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to - * use instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; - - /** - * Path where the maven-wrapper.jar will be saved to. - */ - private static final String MAVEN_WRAPPER_JAR_PATH = - ".mvn/wrapper/maven-wrapper.jar"; - - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; - - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); - - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if (mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); - } catch (IOException e) { - System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if (mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream.close(); - } - } catch (IOException e) { - // Ignore ... - } - } - } - System.out.println("- Downloading from: " + url); - - File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if (!outputFile.getParentFile().exists()) { - if (!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); - } - } - System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); - try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); - System.exit(1); - } - } - - private static void downloadFileFromURL(String urlString, File destination) throws Exception { - if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { - String username = System.getenv("MVNW_USERNAME"); - char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); - Authenticator.setDefault(new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(username, password); - } - }); - } - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(destination); - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); - } - -} diff --git a/task3/APP/.mvn/wrapper/maven-wrapper.jar b/task3/APP/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index 2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50710 zcmbTd1CVCTmM+|7+wQV$+qP}n>auOywyU~q+qUhh+uxis_~*a##hm*_WW?9E7Pb7N%LRFiwbEGCJ0XP=%-6oeT$XZcYgtzC2~q zk(K08IQL8oTl}>>+hE5YRgXTB@fZ4TH9>7=79e`%%tw*SQUa9~$xKD5rS!;ZG@ocK zQdcH}JX?W|0_Afv?y`-NgLum62B&WSD$-w;O6G0Sm;SMX65z)l%m1e-g8Q$QTI;(Q z+x$xth4KFvH@Bs6(zn!iF#nenk^Y^ce;XIItAoCsow38eq?Y-Auh!1in#Rt-_D>H^ z=EjbclGGGa6VnaMGmMLj`x3NcwA43Jb(0gzl;RUIRAUDcR1~99l2SAPkVhoRMMtN} zXvC<tOmX83grD8GSo_Lo?%lNfhD#EBgPo z*nf@ppMC#B!T)Ae0RG$mlJWmGl7CkuU~B8-==5i;rS;8i6rJ=PoQxf446XDX9g|c> zU64ePyMlsI^V5Jq5A+BPe#e73+kpc_r1tv#B)~EZ;7^67F0*QiYfrk0uVW;Qb=NsG zN>gsuCwvb?s-KQIppEaeXtEMdc9dy6Dfduz-tMTms+i01{eD9JE&h?Kht*$eOl#&L zJdM_-vXs(V#$Ed;5wyNWJdPNh+Z$+;$|%qR(t`4W@kDhd*{(7-33BOS6L$UPDeE_53j${QfKN-0v-HG z(QfyvFNbwPK%^!eIo4ac1;b>c0vyf9}Xby@YY!lkz-UvNp zwj#Gg|4B~?n?G^{;(W;|{SNoJbHTMpQJ*Wq5b{l9c8(%?Kd^1?H1om1de0Da9M;Q=n zUfn{f87iVb^>Exl*nZ0hs(Yt>&V9$Pg`zX`AI%`+0SWQ4Zc(8lUDcTluS z5a_KerZWe}a-MF9#Cd^fi!y3%@RFmg&~YnYZ6<=L`UJ0v={zr)>$A;x#MCHZy1st7 ztT+N07NR+vOwSV2pvWuN1%lO!K#Pj0Fr>Q~R40{bwdL%u9i`DSM4RdtEH#cW)6}+I-eE< z&tZs+(Ogu(H_;$a$!7w`MH0r%h&@KM+<>gJL@O~2K2?VrSYUBbhCn#yy?P)uF3qWU z0o09mIik+kvzV6w>vEZy@&Mr)SgxPzUiDA&%07m17udz9usD82afQEps3$pe!7fUf z0eiidkJ)m3qhOjVHC_M(RYCBO%CZKZXFb8}s0-+}@CIn&EF(rRWUX2g^yZCvl0bI} zbP;1S)iXnRC&}5-Tl(hASKqdSnO?ASGJ*MIhOXIblmEudj(M|W!+I3eDc}7t`^mtg z)PKlaXe(OH+q-)qcQ8a@!llRrpGI8DsjhoKvw9T;TEH&?s=LH0w$EzI>%u;oD@x83 zJL7+ncjI9nn!TlS_KYu5vn%f*@qa5F;| zEFxY&B?g=IVlaF3XNm_03PA)=3|{n-UCgJoTr;|;1AU9|kPE_if8!Zvb}0q$5okF$ zHaJdmO&gg!9oN|M{!qGE=tb|3pVQ8PbL$}e;NgXz<6ZEggI}wO@aBP**2Wo=yN#ZC z4G$m^yaM9g=|&!^ft8jOLuzc3Psca*;7`;gnHm}tS0%f4{|VGEwu45KptfNmwxlE~ z^=r30gi@?cOm8kAz!EylA4G~7kbEiRlRIzwrb~{_2(x^$-?|#e6Bi_**(vyr_~9Of z!n>Gqf+Qwiu!xhi9f53=PM3`3tNF}pCOiPU|H4;pzjcsqbwg*{{kyrTxk<;mx~(;; z1NMrpaQ`57yn34>Jo3b|HROE(UNcQash!0p2-!Cz;{IRv#Vp5!3o$P8!%SgV~k&Hnqhp`5eLjTcy93cK!3Hm-$`@yGnaE=?;*2uSpiZTs_dDd51U%i z{|Zd9ou-;laGS_x=O}a+ zB||za<795A?_~Q=r=coQ+ZK@@ zId~hWQL<%)fI_WDIX#=(WNl!Dm$a&ROfLTd&B$vatq!M-2Jcs;N2vps$b6P1(N}=oI3<3luMTmC|0*{ zm1w8bt7vgX($!0@V0A}XIK)w!AzUn7vH=pZEp0RU0p?}ch2XC-7r#LK&vyc2=-#Q2 z^L%8)JbbcZ%g0Du;|8=q8B>X=mIQirpE=&Ox{TiuNDnOPd-FLI^KfEF729!!0x#Es z@>3ursjFSpu%C-8WL^Zw!7a0O-#cnf`HjI+AjVCFitK}GXO`ME&on|^=~Zc}^LBp9 zj=-vlN;Uc;IDjtK38l7}5xxQF&sRtfn4^TNtnzXv4M{r&ek*(eNbIu!u$>Ed%` z5x7+&)2P&4>0J`N&ZP8$vcR+@FS0126s6+Jx_{{`3ZrIMwaJo6jdrRwE$>IU_JTZ} z(||hyyQ)4Z1@wSlT94(-QKqkAatMmkT7pCycEB1U8KQbFX&?%|4$yyxCtm3=W`$4fiG0WU3yI@c zx{wfmkZAYE_5M%4{J-ygbpH|(|GD$2f$3o_Vti#&zfSGZMQ5_f3xt6~+{RX=$H8at z?GFG1Tmp}}lmm-R->ve*Iv+XJ@58p|1_jRvfEgz$XozU8#iJS})UM6VNI!3RUU!{5 zXB(+Eqd-E;cHQ>)`h0(HO_zLmzR3Tu-UGp;08YntWwMY-9i^w_u#wR?JxR2bky5j9 z3Sl-dQQU$xrO0xa&>vsiK`QN<$Yd%YXXM7*WOhnRdSFt5$aJux8QceC?lA0_if|s> ze{ad*opH_kb%M&~(~&UcX0nFGq^MqjxW?HJIP462v9XG>j(5Gat_)#SiNfahq2Mz2 zU`4uV8m$S~o9(W>mu*=h%Gs(Wz+%>h;R9Sg)jZ$q8vT1HxX3iQnh6&2rJ1u|j>^Qf`A76K%_ubL`Zu?h4`b=IyL>1!=*%!_K)=XC z6d}4R5L+sI50Q4P3upXQ3Z!~1ZXLlh!^UNcK6#QpYt-YC=^H=EPg3)z*wXo*024Q4b2sBCG4I# zlTFFY=kQ>xvR+LsuDUAk)q%5pEcqr(O_|^spjhtpb1#aC& zghXzGkGDC_XDa%t(X`E+kvKQ4zrQ*uuQoj>7@@ykWvF332)RO?%AA&Fsn&MNzmFa$ zWk&&^=NNjxLjrli_8ESU)}U|N{%j&TQmvY~lk!~Jh}*=^INA~&QB9em!in_X%Rl1&Kd~Z(u z9mra#<@vZQlOY+JYUwCrgoea4C8^(xv4ceCXcejq84TQ#sF~IU2V}LKc~Xlr_P=ry zl&Hh0exdCbVd^NPCqNNlxM3vA13EI8XvZ1H9#bT7y*U8Y{H8nwGpOR!e!!}*g;mJ#}T{ekSb}5zIPmye*If(}}_=PcuAW#yidAa^9-`<8Gr0 z)Fz=NiZ{)HAvw{Pl5uu)?)&i&Us$Cx4gE}cIJ}B4Xz~-q7)R_%owbP!z_V2=Aq%Rj z{V;7#kV1dNT9-6R+H}}(ED*_!F=~uz>&nR3gb^Ce%+0s#u|vWl<~JD3MvS0T9thdF zioIG3c#Sdsv;LdtRv3ml7%o$6LTVL>(H`^@TNg`2KPIk*8-IB}X!MT0`hN9Ddf7yN z?J=GxPL!uJ7lqwowsl?iRrh@#5C$%E&h~Z>XQcvFC*5%0RN-Opq|=IwX(dq(*sjs+ zqy99+v~m|6T#zR*e1AVxZ8djd5>eIeCi(b8sUk)OGjAsKSOg^-ugwl2WSL@d#?mdl zib0v*{u-?cq}dDGyZ%$XRY=UkQwt2oGu`zQneZh$=^! zj;!pCBWQNtvAcwcWIBM2y9!*W|8LmQy$H~5BEx)78J`4Z0(FJO2P^!YyQU{*Al+fs z){!4JvT1iLrJ8aU3k0t|P}{RN)_^v%$$r;+p0DY7N8CXzmS*HB*=?qaaF9D@#_$SN zSz{moAK<*RH->%r7xX~9gVW$l7?b|_SYI)gcjf0VAUJ%FcQP(TpBs; zg$25D!Ry_`8xpS_OJdeo$qh#7U+cepZ??TII7_%AXsT$B z=e)Bx#v%J0j``00Zk5hsvv6%T^*xGNx%KN-=pocSoqE5_R)OK%-Pbu^1MNzfds)mL zxz^F4lDKV9D&lEY;I+A)ui{TznB*CE$=9(wgE{m}`^<--OzV-5V4X2w9j(_!+jpTr zJvD*y6;39&T+==$F&tsRKM_lqa1HC}aGL0o`%c9mO=fts?36@8MGm7Vi{Y z^<7m$(EtdSr#22<(rm_(l_(`j!*Pu~Y>>xc>I9M#DJYDJNHO&4=HM%YLIp?;iR&$m z#_$ZWYLfGLt5FJZhr3jpYb`*%9S!zCG6ivNHYzNHcI%khtgHBliM^Ou}ZVD7ehU9 zS+W@AV=?Ro!=%AJ>Kcy9aU3%VX3|XM_K0A+ZaknKDyIS3S-Hw1C7&BSW5)sqj5Ye_ z4OSW7Yu-;bCyYKHFUk}<*<(@TH?YZPHr~~Iy%9@GR2Yd}J2!N9K&CN7Eq{Ka!jdu; zQNB*Y;i(7)OxZK%IHGt#Rt?z`I|A{q_BmoF!f^G}XVeTbe1Wnzh%1g>j}>DqFf;Rp zz7>xIs12@Ke0gr+4-!pmFP84vCIaTjqFNg{V`5}Rdt~xE^I;Bxp4)|cs8=f)1YwHz zqI`G~s2~qqDV+h02b`PQpUE#^^Aq8l%y2|ByQeXSADg5*qMprEAE3WFg0Q39`O+i1 z!J@iV!`Y~C$wJ!5Z+j5$i<1`+@)tBG$JL=!*uk=2k;T<@{|s1$YL079FvK%mPhyHV zP8^KGZnp`(hVMZ;s=n~3r2y;LTwcJwoBW-(ndU-$03{RD zh+Qn$ja_Z^OuMf3Ub|JTY74s&Am*(n{J3~@#OJNYuEVVJd9*H%)oFoRBkySGm`hx! zT3tG|+aAkXcx-2Apy)h^BkOyFTWQVeZ%e2@;*0DtlG9I3Et=PKaPt&K zw?WI7S;P)TWED7aSH$3hL@Qde?H#tzo^<(o_sv_2ci<7M?F$|oCFWc?7@KBj-;N$P zB;q!8@bW-WJY9do&y|6~mEruZAVe$!?{)N9rZZxD-|oltkhW9~nR8bLBGXw<632!l z*TYQn^NnUy%Ds}$f^=yQ+BM-a5X4^GHF=%PDrRfm_uqC zh{sKwIu|O0&jWb27;wzg4w5uA@TO_j(1X?8E>5Zfma|Ly7Bklq|s z9)H`zoAGY3n-+&JPrT!>u^qg9Evx4y@GI4$n-Uk_5wttU1_t?6><>}cZ-U+&+~JE) zPlDbO_j;MoxdLzMd~Ew|1o^a5q_1R*JZ=#XXMzg?6Zy!^hop}qoLQlJ{(%!KYt`MK z8umEN@Z4w!2=q_oe=;QttPCQy3Nm4F@x>@v4sz_jo{4m*0r%J(w1cSo;D_hQtJs7W z><$QrmG^+<$4{d2bgGo&3-FV}avg9zI|Rr(k{wTyl3!M1q+a zD9W{pCd%il*j&Ft z5H$nENf>>k$;SONGW`qo6`&qKs*T z2^RS)pXk9b@(_Fw1bkb)-oqK|v}r$L!W&aXA>IpcdNZ_vWE#XO8X`#Yp1+?RshVcd zknG%rPd*4ECEI0wD#@d+3NbHKxl}n^Sgkx==Iu%}HvNliOqVBqG?P2va zQ;kRJ$J6j;+wP9cS za#m;#GUT!qAV%+rdWolk+)6kkz4@Yh5LXP+LSvo9_T+MmiaP-eq6_k;)i6_@WSJ zlT@wK$zqHu<83U2V*yJ|XJU4farT#pAA&@qu)(PO^8PxEmPD4;Txpio+2)#!9 z>&=i7*#tc0`?!==vk>s7V+PL#S1;PwSY?NIXN2=Gu89x(cToFm))7L;< z+bhAbVD*bD=}iU`+PU+SBobTQ%S!=VL!>q$rfWsaaV}Smz>lO9JXT#`CcH_mRCSf4%YQAw`$^yY z3Y*^Nzk_g$xn7a_NO(2Eb*I=^;4f!Ra#Oo~LLjlcjke*k*o$~U#0ZXOQ5@HQ&T46l z7504MUgZkz2gNP1QFN8Y?nSEnEai^Rgyvl}xZfMUV6QrJcXp;jKGqB=D*tj{8(_pV zqyB*DK$2lgYGejmJUW)*s_Cv65sFf&pb(Yz8oWgDtQ0~k^0-wdF|tj}MOXaN@ydF8 zNr={U?=;&Z?wr^VC+`)S2xl}QFagy;$mG=TUs7Vi2wws5zEke4hTa2)>O0U?$WYsZ z<8bN2bB_N4AWd%+kncgknZ&}bM~eDtj#C5uRkp21hWW5gxWvc6b*4+dn<{c?w9Rmf zIVZKsPl{W2vQAlYO3yh}-{Os=YBnL8?uN5(RqfQ=-1cOiUnJu>KcLA*tQK3FU`_bM zM^T28w;nAj5EdAXFi&Kk1Nnl2)D!M{@+D-}bIEe+Lc4{s;YJc-{F#``iS2uk;2!Zp zF9#myUmO!wCeJIoi^A+T^e~20c+c2C}XltaR!|U-HfDA=^xF97ev}$l6#oY z&-&T{egB)&aV$3_aVA51XGiU07$s9vubh_kQG?F$FycvS6|IO!6q zq^>9|3U^*!X_C~SxX&pqUkUjz%!j=VlXDo$!2VLH!rKj@61mDpSr~7B2yy{>X~_nc zRI+7g2V&k zd**H++P9dg!-AOs3;GM`(g<+GRV$+&DdMVpUxY9I1@uK28$az=6oaa+PutlO9?6#? zf-OsgT>^@8KK>ggkUQRPPgC7zjKFR5spqQb3ojCHzj^(UH~v+!y*`Smv)VpVoPwa6 zWG18WJaPKMi*F6Zdk*kU^`i~NNTfn3BkJniC`yN98L-Awd)Z&mY? zprBW$!qL-OL7h@O#kvYnLsfff@kDIegt~?{-*5A7JrA;#TmTe?jICJqhub-G@e??D zqiV#g{)M!kW1-4SDel7TO{;@*h2=_76g3NUD@|c*WO#>MfYq6_YVUP+&8e4|%4T`w zXzhmVNziAHazWO2qXcaOu@R1MrPP{t)`N)}-1&~mq=ZH=w=;-E$IOk=y$dOls{6sRR`I5>|X zpq~XYW4sd;J^6OwOf**J>a7u$S>WTFPRkjY;BfVgQst)u4aMLR1|6%)CB^18XCz+r ztkYQ}G43j~Q&1em(_EkMv0|WEiKu;z2zhb(L%$F&xWwzOmk;VLBYAZ8lOCziNoPw1 zv2BOyXA`A8z^WH!nXhKXM`t0;6D*-uGds3TYGrm8SPnJJOQ^fJU#}@aIy@MYWz**H zvkp?7I5PE{$$|~{-ZaFxr6ZolP^nL##mHOErB^AqJqn^hFA=)HWj!m3WDaHW$C)i^ z9@6G$SzB=>jbe>4kqr#sF7#K}W*Cg-5y6kun3u&0L7BpXF9=#7IN8FOjWrWwUBZiU zT_se3ih-GBKx+Uw0N|CwP3D@-C=5(9T#BH@M`F2!Goiqx+Js5xC92|Sy0%WWWp={$(am!#l~f^W_oz78HX<0X#7 zp)p1u~M*o9W@O8P{0Qkg@Wa# z2{Heb&oX^CQSZWSFBXKOfE|tsAm#^U-WkDnU;IowZ`Ok4!mwHwH=s|AqZ^YD4!5!@ zPxJj+Bd-q6w_YG`z_+r;S86zwXb+EO&qogOq8h-Ect5(M2+>(O7n7)^dP*ws_3U6v zVsh)sk^@*c>)3EML|0<-YROho{lz@Nd4;R9gL{9|64xVL`n!m$-Jjrx?-Bacp!=^5 z1^T^eB{_)Y<9)y{-4Rz@9_>;_7h;5D+@QcbF4Wv7hu)s0&==&6u)33 zHRj+&Woq-vDvjwJCYES@$C4{$?f$Ibi4G()UeN11rgjF+^;YE^5nYprYoJNoudNj= zm1pXSeG64dcWHObUetodRn1Fw|1nI$D9z}dVEYT0lQnsf_E1x2vBLql7NrHH!n&Sq z6lc*mvU=WS6=v9Lrl}&zRiu_6u;6g%_DU{9b+R z#YHqX7`m9eydf?KlKu6Sb%j$%_jmydig`B*TN`cZL-g!R)iE?+Q5oOqBFKhx z%MW>BC^(F_JuG(ayE(MT{S3eI{cKiwOtPwLc0XO*{*|(JOx;uQOfq@lp_^cZo=FZj z4#}@e@dJ>Bn%2`2_WPeSN7si^{U#H=7N4o%Dq3NdGybrZgEU$oSm$hC)uNDC_M9xc zGzwh5Sg?mpBIE8lT2XsqTt3j3?We8}3bzLBTQd639vyg^$0#1epq8snlDJP2(BF)K zSx30RM+{f+b$g{9usIL8H!hCO117Xgv}ttPJm9wVRjPk;ePH@zxv%j9k5`TzdXLeT zFgFX`V7cYIcBls5WN0Pf6SMBN+;CrQ(|EsFd*xtwr#$R{Z9FP`OWtyNsq#mCgZ7+P z^Yn$haBJ)r96{ZJd8vlMl?IBxrgh=fdq_NF!1{jARCVz>jNdC)H^wfy?R94#MPdUjcYX>#wEx+LB#P-#4S-%YH>t-j+w zOFTI8gX$ard6fAh&g=u&56%3^-6E2tpk*wx3HSCQ+t7+*iOs zPk5ysqE}i*cQocFvA68xHfL|iX(C4h*67@3|5Qwle(8wT&!&{8*{f%0(5gH+m>$tq zp;AqrP7?XTEooYG1Dzfxc>W%*CyL16q|fQ0_jp%%Bk^k!i#Nbi(N9&T>#M{gez_Ws zYK=l}adalV(nH}I_!hNeb;tQFk3BHX7N}}R8%pek^E`X}%ou=cx8InPU1EE0|Hen- zyw8MoJqB5=)Z%JXlrdTXAE)eqLAdVE-=>wGHrkRet}>3Yu^lt$Kzu%$3#(ioY}@Gu zjk3BZuQH&~7H+C*uX^4}F*|P89JX;Hg2U!pt>rDi(n(Qe-c}tzb0#6_ItoR0->LSt zR~UT<-|@TO%O`M+_e_J4wx7^)5_%%u+J=yF_S#2Xd?C;Ss3N7KY^#-vx+|;bJX&8r zD?|MetfhdC;^2WG`7MCgs>TKKN=^=!x&Q~BzmQio_^l~LboTNT=I zC5pme^P@ER``p$2md9>4!K#vV-Fc1an7pl>_|&>aqP}+zqR?+~Z;f2^`a+-!Te%V? z;H2SbF>jP^GE(R1@%C==XQ@J=G9lKX+Z<@5}PO(EYkJh=GCv#)Nj{DkWJM2}F&oAZ6xu8&g7pn1ps2U5srwQ7CAK zN&*~@t{`31lUf`O;2w^)M3B@o)_mbRu{-`PrfNpF!R^q>yTR&ETS7^-b2*{-tZAZz zw@q5x9B5V8Qd7dZ!Ai$9hk%Q!wqbE1F1c96&zwBBaRW}(^axoPpN^4Aw}&a5dMe+*Gomky_l^54*rzXro$ z>LL)U5Ry>~FJi=*{JDc)_**c)-&faPz`6v`YU3HQa}pLtb5K)u%K+BOqXP0)rj5Au$zB zW1?vr?mDv7Fsxtsr+S6ucp2l#(4dnr9sD*v+@*>g#M4b|U?~s93>Pg{{a5|rm2xfI z`>E}?9S@|IoUX{Q1zjm5YJT|3S>&09D}|2~BiMo=z4YEjXlWh)V&qs;*C{`UMxp$9 zX)QB?G$fPD6z5_pNs>Jeh{^&U^)Wbr?2D6-q?)`*1k@!UvwQgl8eG$r+)NnFoT)L6 zg7lEh+E6J17krfYJCSjWzm67hEth24pomhz71|Qodn#oAILN)*Vwu2qpJirG)4Wnv}9GWOFrQg%Je+gNrPl8mw7ykE8{ z=|B4+uwC&bpp%eFcRU6{mxRV32VeH8XxX>v$du<$(DfinaaWxP<+Y97Z#n#U~V zVEu-GoPD=9$}P;xv+S~Ob#mmi$JQmE;Iz4(){y*9pFyW-jjgdk#oG$fl4o9E8bo|L zWjo4l%n51@Kz-n%zeSCD`uB?T%FVk+KBI}=ve zvlcS#wt`U6wrJo}6I6Rwb=1GzZfwE=I&Ne@p7*pH84XShXYJRgvK)UjQL%R9Zbm(m zxzTQsLTON$WO7vM)*vl%Pc0JH7WhP;$z@j=y#avW4X8iqy6mEYr@-}PW?H)xfP6fQ z&tI$F{NNct4rRMSHhaelo<5kTYq+(?pY)Ieh8*sa83EQfMrFupMM@nfEV@EmdHUv9 z35uzIrIuo4#WnF^_jcpC@uNNaYTQ~uZWOE6P@LFT^1@$o&q+9Qr8YR+ObBkpP9=F+$s5+B!mX2~T zAuQ6RenX?O{IlLMl1%)OK{S7oL}X%;!XUxU~xJN8xk z`xywS*naF(J#?vOpB(K=o~lE;m$zhgPWDB@=p#dQIW>xe_p1OLoWInJRKbEuoncf; zmS1!u-ycc1qWnDg5Nk2D)BY%jmOwCLC+Ny>`f&UxFowIsHnOXfR^S;&F(KXd{ODlm z$6#1ccqt-HIH9)|@fHnrKudu!6B$_R{fbCIkSIb#aUN|3RM>zuO>dpMbROZ`^hvS@ z$FU-;e4W}!ubzKrU@R*dW*($tFZ>}dd*4_mv)#O>X{U@zSzQt*83l9mI zI$8O<5AIDx`wo0}f2fsPC_l>ONx_`E7kdXu{YIZbp1$(^oBAH({T~&oQ&1{X951QW zmhHUxd)t%GQ9#ak5fTjk-cahWC;>^Rg7(`TVlvy0W@Y!Jc%QL3Ozu# zDPIqBCy&T2PWBj+d-JA-pxZlM=9ja2ce|3B(^VCF+a*MMp`(rH>Rt6W1$;r{n1(VK zLs>UtkT43LR2G$AOYHVailiqk7naz2yZGLo*xQs!T9VN5Q>eE(w zw$4&)&6xIV$IO^>1N-jrEUg>O8G4^@y+-hQv6@OmF@gy^nL_n1P1-Rtyy$Bl;|VcV zF=p*&41-qI5gG9UhKmmnjs932!6hceXa#-qfK;3d*a{)BrwNFeKU|ge?N!;zk+kB! zMD_uHJR#%b54c2tr~uGPLTRLg$`fupo}cRJeTwK;~}A>(Acy4k-Xk&Aa1&eWYS1ULWUj@fhBiWY$pdfy+F z@G{OG{*v*mYtH3OdUjwEr6%_ZPZ3P{@rfbNPQG!BZ7lRyC^xlMpWH`@YRar`tr}d> z#wz87t?#2FsH-jM6m{U=gp6WPrZ%*w0bFm(T#7m#v^;f%Z!kCeB5oiF`W33W5Srdt zdU?YeOdPG@98H7NpI{(uN{FJdu14r(URPH^F6tOpXuhU7T9a{3G3_#Ldfx_nT(Hec zo<1dyhsVsTw;ZkVcJ_0-h-T3G1W@q)_Q30LNv)W?FbMH+XJ* zy=$@39Op|kZv`Rt>X`zg&at(?PO^I=X8d9&myFEx#S`dYTg1W+iE?vt#b47QwoHI9 zNP+|3WjtXo{u}VG(lLUaW0&@yD|O?4TS4dfJI`HC-^q;M(b3r2;7|FONXphw-%7~* z&;2!X17|05+kZOpQ3~3!Nb>O94b&ZSs%p)TK)n3m=4eiblVtSx@KNFgBY_xV6ts;NF;GcGxMP8OKV^h6LmSb2E#Qnw ze!6Mnz7>lE9u{AgQ~8u2zM8CYD5US8dMDX-5iMlgpE9m*s+Lh~A#P1er*rF}GHV3h z=`STo?kIXw8I<`W0^*@mB1$}pj60R{aJ7>C2m=oghKyxMbFNq#EVLgP0cH3q7H z%0?L93-z6|+jiN|@v>ix?tRBU(v-4RV`}cQH*fp|)vd3)8i9hJ3hkuh^8dz{F5-~_ zUUr1T3cP%cCaTooM8dj|4*M=e6flH0&8ve32Q)0dyisl))XkZ7Wg~N}6y`+Qi2l+e zUd#F!nJp{#KIjbQdI`%oZ`?h=5G^kZ_uN`<(`3;a!~EMsWV|j-o>c?x#;zR2ktiB! z);5rrHl?GPtr6-o!tYd|uK;Vbsp4P{v_4??=^a>>U4_aUXPWQ$FPLE4PK$T^3Gkf$ zHo&9$U&G`d(Os6xt1r?sg14n)G8HNyWa^q8#nf0lbr4A-Fi;q6t-`pAx1T*$eKM*$ z|CX|gDrk#&1}>5H+`EjV$9Bm)Njw&7-ZR{1!CJTaXuP!$Pcg69`{w5BRHysB$(tWUes@@6aM69kb|Lx$%BRY^-o6bjH#0!7b;5~{6J+jKxU!Kmi# zndh@+?}WKSRY2gZ?Q`{(Uj|kb1%VWmRryOH0T)f3cKtG4oIF=F7RaRnH0Rc_&372={_3lRNsr95%ZO{IX{p@YJ^EI%+gvvKes5cY+PE@unghjdY5#9A!G z70u6}?zmd?v+{`vCu-53_v5@z)X{oPC@P)iA3jK$`r zSA2a7&!^zmUiZ82R2=1cumBQwOJUPz5Ay`RLfY(EiwKkrx%@YN^^XuET;tE zmr-6~I7j!R!KrHu5CWGSChO6deaLWa*9LLJbcAJsFd%Dy>a!>J`N)Z&oiU4OEP-!Ti^_!p}O?7`}i7Lsf$-gBkuY*`Zb z7=!nTT;5z$_5$=J=Ko+Cp|Q0J=%oFr>hBgnL3!tvFoLNhf#D0O=X^h+x08iB;@8pXdRHxX}6R4k@i6%vmsQwu^5z zk1ip`#^N)^#Lg#HOW3sPI33xqFB4#bOPVnY%d6prwxf;Y-w9{ky4{O6&94Ra8VN@K zb-lY;&`HtxW@sF!doT5T$2&lIvJpbKGMuDAFM#!QPXW87>}=Q4J3JeXlwHys?!1^#37q_k?N@+u&Ns20pEoBeZC*np;i;M{2C0Z4_br2gsh6eL z#8`#sn41+$iD?^GL%5?cbRcaa-Nx0vE(D=*WY%rXy3B%gNz0l?#noGJGP728RMY#q z=2&aJf@DcR?QbMmN)ItUe+VM_U!ryqA@1VVt$^*xYt~-qvW!J4Tp<-3>jT=7Zow5M z8mSKp0v4b%a8bxFr>3MwZHSWD73D@+$5?nZAqGM#>H@`)mIeC#->B)P8T$zh-Pxnc z8)~Zx?TWF4(YfKuF3WN_ckpCe5;x4V4AA3(i$pm|78{%!q?|~*eH0f=?j6i)n~Hso zmTo>vqEtB)`%hP55INf7HM@taH)v`Fw40Ayc*R!T?O{ziUpYmP)AH`euTK!zg9*6Z z!>M=$3pd0!&TzU=hc_@@^Yd3eUQpX4-33}b{?~5t5lgW=ldJ@dUAH%`l5US1y_`40 zs(X`Qk}vvMDYYq+@Rm+~IyCX;iD~pMgq^KY)T*aBz@DYEB={PxA>)mI6tM*sx-DmGQHEaHwRrAmNjO!ZLHO4b;;5mf@zzlPhkP($JeZGE7 z?^XN}Gf_feGoG~BjUgVa*)O`>lX=$BSR2)uD<9 z>o^|nb1^oVDhQbfW>>!;8-7<}nL6L^V*4pB=>wwW+RXAeRvKED(n1;R`A6v$6gy0I(;Vf?!4;&sgn7F%LpM}6PQ?0%2Z@b{It<(G1CZ|>913E0nR2r^Pa*Bp z@tFGi*CQ~@Yc-?{cwu1 zsilf=k^+Qs>&WZG(3WDixisHpR>`+ihiRwkL(3T|=xsoNP*@XX3BU8hr57l3k;pni zI``=3Nl4xh4oDj<%>Q1zYXHr%Xg_xrK3Nq?vKX3|^Hb(Bj+lONTz>4yhU-UdXt2>j z<>S4NB&!iE+ao{0Tx^N*^|EZU;0kJkx@zh}S^P{ieQjGl468CbC`SWnwLRYYiStXm zOxt~Rb3D{dz=nHMcY)#r^kF8|q8KZHVb9FCX2m^X*(|L9FZg!5a7((!J8%MjT$#Fs)M1Pb zq6hBGp%O1A+&%2>l0mpaIzbo&jc^!oN^3zxap3V2dNj3x<=TwZ&0eKX5PIso9j1;e zwUg+C&}FJ`k(M|%%}p=6RPUq4sT3-Y;k-<68ciZ~_j|bt>&9ZLHNVrp#+pk}XvM{8 z`?k}o-!if>hVlCP9j%&WI2V`5SW)BCeR5>MQhF)po=p~AYN%cNa_BbV6EEh_kk^@a zD>4&>uCGCUmyA-c)%DIcF4R6!>?6T~Mj_m{Hpq`*(wj>foHL;;%;?(((YOxGt)Bhx zuS+K{{CUsaC++%}S6~CJ=|vr(iIs-je)e9uJEU8ZJAz)w166q)R^2XI?@E2vUQ!R% zn@dxS!JcOimXkWJBz8Y?2JKQr>`~SmE2F2SL38$SyR1^yqj8_mkBp)o$@+3BQ~Mid z9U$XVqxX3P=XCKj0*W>}L0~Em`(vG<>srF8+*kPrw z20{z(=^w+ybdGe~Oo_i|hYJ@kZl*(9sHw#Chi&OIc?w`nBODp?ia$uF%Hs(X>xm?j zqZQ`Ybf@g#wli`!-al~3GWiE$K+LCe=Ndi!#CVjzUZ z!sD2O*;d28zkl))m)YN7HDi^z5IuNo3^w(zy8 zszJG#mp#Cj)Q@E@r-=NP2FVxxEAeOI2e=|KshybNB6HgE^(r>HD{*}S}mO>LuRGJT{*tfTzw_#+er-0${}%YPe@CMJ1Ng#j#)i)SnY@ss3gL;g zg2D~#Kpdfu#G;q1qz_TwSz1VJT(b3zby$Vk&;Y#1(A)|xj`_?i5YQ;TR%jice5E;0 zYHg;`zS5{S*9xI6o^j>rE8Ua*XhIw{_-*&@(R|C(am8__>+Ws&Q^ymy*X4~hR2b5r zm^p3sw}yv=tdyncy_Ui7{BQS732et~Z_@{-IhHDXAV`(Wlay<#hb>%H%WDi+K$862nA@BDtM#UCKMu+kM`!JHyWSi?&)A7_ z3{cyNG%a~nnH_!+;g&JxEMAmh-Z}rC!o7>OVzW&PoMyTA_g{hqXG)SLraA^OP**<7 zjWbr7z!o2n3hnx7A=2O=WL;`@9N{vQIM@&|G-ljrPvIuJHYtss0Er0fT5cMXNUf1B z7FAwBDixt0X7C3S)mPe5g`YtME23wAnbU)+AtV}z+e8G;0BP=bI;?(#|Ep!vVfDbK zvx+|CKF>yt0hWQ3drchU#XBU+HiuG*V^snFAPUp-5<#R&BUAzoB!aZ+e*KIxa26V}s6?nBK(U-7REa573wg-jqCg>H8~>O{ z*C0JL-?X-k_y%hpUFL?I>0WV{oV`Nb)nZbJG01R~AG>flIJf)3O*oB2i8~;!P?Wo_ z0|QEB*fifiL6E6%>tlAYHm2cjTFE@*<);#>689Z6S#BySQ@VTMhf9vYQyLeDg1*F} zjq>i1*x>5|CGKN{l9br3kB0EHY|k4{%^t7-uhjd#NVipUZa=EUuE5kS1_~qYX?>hJ z$}!jc9$O$>J&wnu0SgfYods^z?J4X;X7c77Me0kS-dO_VUQ39T(Kv(Y#s}Qqz-0AH z^?WRL(4RzpkD+T5FG_0NyPq-a-B7A5LHOCqwObRJi&oRi(<;OuIN7SV5PeHU$<@Zh zPozEV`dYmu0Z&Tqd>t>8JVde9#Pt+l95iHe$4Xwfy1AhI zDM4XJ;bBTTvRFtW>E+GzkN)9k!hA5z;xUOL2 zq4}zn-DP{qc^i|Y%rvi|^5k-*8;JZ~9a;>-+q_EOX+p1Wz;>i7c}M6Nv`^NY&{J-> z`(mzDJDM}QPu5i44**2Qbo(XzZ-ZDu%6vm8w@DUarqXj41VqP~ zs&4Y8F^Waik3y1fQo`bVUH;b=!^QrWb)3Gl=QVKr+6sxc=ygauUG|cm?|X=;Q)kQ8 zM(xrICifa2p``I7>g2R~?a{hmw@{!NS5`VhH8+;cV(F>B94M*S;5#O`YzZH1Z%yD? zZ61w(M`#aS-*~Fj;x|J!KM|^o;MI#Xkh0ULJcA?o4u~f%Z^16ViA27FxU5GM*rKq( z7cS~MrZ=f>_OWx8j#-Q3%!aEU2hVuTu(7`TQk-Bi6*!<}0WQi;_FpO;fhpL4`DcWp zGOw9vx0N~6#}lz(r+dxIGZM3ah-8qrqMmeRh%{z@dbUD2w15*_4P?I~UZr^anP}DB zU9CCrNiy9I3~d#&!$DX9e?A});BjBtQ7oGAyoI$8YQrkLBIH@2;lt4E^)|d6Jwj}z z&2_E}Y;H#6I4<10d_&P0{4|EUacwFHauvrjAnAm6yeR#}f}Rk27CN)vhgRqEyPMMS7zvunj2?`f;%?alsJ+-K+IzjJx>h8 zu~m_y$!J5RWAh|C<6+uiCNsOKu)E72M3xKK(a9Okw3e_*O&}7llNV!=P87VM2DkAk zci!YXS2&=P0}Hx|wwSc9JP%m8dMJA*q&VFB0yMI@5vWoAGraygwn){R+Cj6B1a2Px z5)u(K5{+;z2n*_XD!+Auv#LJEM)(~Hx{$Yb^ldQmcYF2zNH1V30*)CN_|1$v2|`LnFUT$%-tO0Eg|c5$BB~yDfzS zcOXJ$wpzVK0MfTjBJ0b$r#_OvAJ3WRt+YOLlJPYMx~qp>^$$$h#bc|`g0pF-Ao43? z>*A+8lx>}L{p(Tni2Vvk)dtzg$hUKjSjXRagj)$h#8=KV>5s)J4vGtRn5kP|AXIz! zPgbbVxW{2o4s-UM;c#We8P&mPN|DW7_uLF!a|^0S=wr6Esx9Z$2|c1?GaupU6$tb| zY_KU`(_29O_%k(;>^|6*pZURH3`@%EuKS;Ns z1lujmf;r{qAN&Q0&m{wJSZ8MeE7RM5+Sq;ul_ z`+ADrd_Um+G37js6tKsArNB}n{p*zTUxQr>3@wA;{EUbjNjlNd6$Mx zg0|MyU)v`sa~tEY5$en7^PkC=S<2@!nEdG6L=h(vT__0F=S8Y&eM=hal#7eM(o^Lu z2?^;05&|CNliYrq6gUv;|i!(W{0N)LWd*@{2q*u)}u*> z7MQgk6t9OqqXMln?zoMAJcc zMKaof_Up})q#DzdF?w^%tTI7STI^@8=Wk#enR*)&%8yje>+tKvUYbW8UAPg55xb70 zEn5&Ba~NmOJlgI#iS8W3-@N%>V!#z-ZRwfPO1)dQdQkaHsiqG|~we2ALqG7Ruup(DqSOft2RFg_X%3w?6VqvV1uzX_@F(diNVp z4{I|}35=11u$;?|JFBEE*gb;T`dy+8gWJ9~pNsecrO`t#V9jW-6mnfO@ff9od}b(3s4>p0i30gbGIv~1@a^F2kl7YO;DxmF3? zWi-RoXhzRJV0&XE@ACc?+@6?)LQ2XNm4KfalMtsc%4!Fn0rl zpHTrHwR>t>7W?t!Yc{*-^xN%9P0cs0kr=`?bQ5T*oOo&VRRu+1chM!qj%2I!@+1XF z4GWJ=7ix9;Wa@xoZ0RP`NCWw0*8247Y4jIZ>GEW7zuoCFXl6xIvz$ezsWgKdVMBH> z{o!A7f;R-@eK9Vj7R40xx)T<2$?F2E<>Jy3F;;=Yt}WE59J!1WN367 zA^6pu_zLoZIf*x031CcwotS{L8bJE(<_F%j_KJ2P_IusaZXwN$&^t716W{M6X2r_~ zaiMwdISX7Y&Qi&Uh0upS3TyEIXNDICQlT5fHXC`aji-c{U(J@qh-mWl-uMN|T&435 z5)a1dvB|oe%b2mefc=Vpm0C%IUYYh7HI*;3UdgNIz}R##(#{(_>82|zB0L*1i4B5j-xi9O4x10rs_J6*gdRBX=@VJ+==sWb&_Qc6tSOowM{BX@(zawtjl zdU!F4OYw2@Tk1L^%~JCwb|e#3CC>srRHQ*(N%!7$Mu_sKh@|*XtR>)BmWw!;8-mq7 zBBnbjwx8Kyv|hd*`5}84flTHR1Y@@uqjG`UG+jN_YK&RYTt7DVwfEDXDW4U+iO{>K zw1hr{_XE*S*K9TzzUlJH2rh^hUm2v7_XjwTuYap|>zeEDY$HOq3X4Tz^X}E9z)x4F zs+T?Ed+Hj<#jY-`Va~fT2C$=qFT-5q$@p9~0{G&eeL~tiIAHXA!f6C(rAlS^)&k<- zXU|ZVs}XQ>s5iONo~t!XXZgtaP$Iau;JT%h)>}v54yut~pykaNye4axEK#5@?TSsQ zE;Jvf9I$GVb|S`7$pG)4vgo9NXsKr?u=F!GnA%VS2z$@Z(!MR9?EPcAqi5ft)Iz6sNl`%kj+_H-X`R<>BFrBW=fSlD|{`D%@Rcbu2?%>t7i34k?Ujb)2@J-`j#4 zLK<69qcUuniIan-$A1+fR=?@+thwDIXtF1Tks@Br-xY zfB+zblrR(ke`U;6U~-;p1Kg8Lh6v~LjW@9l2P6s+?$2!ZRPX`(ZkRGe7~q(4&gEi<$ch`5kQ?*1=GSqkeV z{SA1EaW_A!t{@^UY2D^YO0(H@+kFVzZaAh0_`A`f(}G~EP~?B|%gtxu&g%^x{EYSz zk+T;_c@d;+n@$<>V%P=nk36?L!}?*=vK4>nJSm+1%a}9UlmTJTrfX4{Lb7smNQn@T zw9p2%(Zjl^bWGo1;DuMHN(djsEm)P8mEC2sL@KyPjwD@d%QnZ$ zMJ3cnn!_!iP{MzWk%PI&D?m?C(y2d|2VChluN^yHya(b`h>~GkI1y;}O_E57zOs!{ zt2C@M$^PR2U#(dZmA-sNreB@z-yb0Bf7j*yONhZG=onhx>t4)RB`r6&TP$n zgmN*)eCqvgriBO-abHQ8ECN0bw?z5Bxpx z=jF@?zFdVn?@gD5egM4o$m`}lV(CWrOKKq(sv*`mNcHcvw&Xryfw<{ch{O&qc#WCTXX6=#{MV@q#iHYba!OUY+MGeNTjP%Fj!WgM&`&RlI^=AWTOqy-o zHo9YFt!gQ*p7{Fl86>#-JLZo(b^O`LdFK~OsZBRR@6P?ad^Ujbqm_j^XycM4ZHFyg ziUbIFW#2tj`65~#2V!4z7DM8Z;fG0|APaQ{a2VNYpNotB7eZ5kp+tPDz&Lqs0j%Y4tA*URpcfi z_M(FD=fRGdqf430j}1z`O0I=;tLu81bwJXdYiN7_&a-?ly|-j*+=--XGvCq#32Gh(=|qj5F?kmihk{%M&$}udW5)DHK zF_>}5R8&&API}o0osZJRL3n~>76nUZ&L&iy^s>PMnNcYZ|9*1$v-bzbT3rpWsJ+y{ zPrg>5Zlery96Um?lc6L|)}&{992{_$J&=4%nRp9BAC6!IB=A&=tF>r8S*O-=!G(_( zwXbX_rGZgeiK*&n5E;f=k{ktyA1(;x_kiMEt0*gpp_4&(twlS2e5C?NoD{n>X2AT# zY@Zp?#!b1zNq96MQqeO*M1MMBin5v#RH52&Xd~DO6-BZLnA6xO1$sou(YJ1Dlc{WF zVa%2DyYm`V#81jP@70IJ;DX@y*iUt$MLm)ByAD$eUuji|5{ptFYq(q)mE(5bOpxjM z^Q`AHWq44SG3`_LxC9fwR)XRVIp=B%<(-lOC3jI#bb@dK(*vjom!=t|#<@dZql%>O z15y^{4tQoeW9Lu%G&V$90x6F)xN6y_oIn;!Q zs)8jT$;&;u%Y>=T3hg34A-+Y*na=|glcStr5D;&5*t5*DmD~x;zQAV5{}Ya`?RRGa zT*t9@$a~!co;pD^!J5bo?lDOWFx%)Y=-fJ+PDGc0>;=q=s?P4aHForSB+)v0WY2JH z?*`O;RHum6j%#LG)Vu#ciO#+jRC3!>T(9fr+XE7T2B7Z|0nR5jw@WG)kDDzTJ=o4~ zUpeyt7}_nd`t}j9BKqryOha{34erm)RmST)_9Aw)@ zHbiyg5n&E{_CQR@h<}34d7WM{s{%5wdty1l+KX8*?+-YkNK2Be*6&jc>@{Fd;Ps|| z26LqdI3#9le?;}risDq$K5G3yoqK}C^@-8z^wj%tdgw-6@F#Ju{Sg7+y)L?)U$ez> zoOaP$UFZ?y5BiFycir*pnaAaY+|%1%8&|(@VB)zweR%?IidwJyK5J!STzw&2RFx zZV@qeaCB01Hu#U9|1#=Msc8Pgz5P*4Lrp!Q+~(G!OiNR{qa7|r^H?FC6gVhkk3y7=uW#Sh;&>78bZ}aK*C#NH$9rX@M3f{nckYI+5QG?Aj1DM)@~z_ zw!UAD@gedTlePB*%4+55naJ8ak_;))#S;4ji!LOqY5VRI){GMwHR~}6t4g>5C_#U# ztYC!tjKjrKvRy=GAsJVK++~$|+s!w9z3H4G^mACv=EErXNSmH7qN}%PKcN|8%9=i)qS5+$L zu&ya~HW%RMVJi4T^pv?>mw*Gf<)-7gf#Qj|e#w2|v4#t!%Jk{&xlf;$_?jW*n!Pyx zkG$<18kiLOAUPuFfyu-EfWX%4jYnjBYc~~*9JEz6oa)_R|8wjZA|RNrAp%}14L7fW zi7A5Wym*K+V8pkqqO-X#3ft{0qs?KVt^)?kS>AicmeO&q+~J~ zp0YJ_P~_a8j= zsAs~G=8F=M{4GZL{|B__UorX@MRNQLn?*_gym4aW(~+i13knnk1P=khoC-ViMZk+x zLW(l}oAg1H`dU+Fv**;qw|ANDSRs>cGqL!Yw^`; zv;{E&8CNJcc)GHzTYM}f&NPw<6j{C3gaeelU#y!M)w-utYEHOCCJo|Vgp7K6C_$14 zqIrLUB0bsgz^D%V%fbo2f9#yb#CntTX?55Xy|Kps&Xek*4_r=KDZ z+`TQuv|$l}MWLzA5Ay6Cvsa^7xvwXpy?`w(6vx4XJ zWuf1bVSb#U8{xlY4+wlZ$9jjPk)X_;NFMqdgq>m&W=!KtP+6NL57`AMljW+es zzqjUjgz;V*kktJI?!NOg^s_)ph45>4UDA!Vo0hn>KZ+h-3=?Y3*R=#!fOX zP$Y~+14$f66ix?UWB_6r#fMcC^~X4R-<&OD1CSDNuX~y^YwJ>sW0j`T<2+3F9>cLo z#!j57$ll2K9(%$4>eA7(>FJX5e)pR5&EZK!IMQzOfik#FU*o*LGz~7u(8}XzIQRy- z!U7AlMTIe|DgQFmc%cHy_9^{o`eD%ja_L>ckU6$O4*U**o5uR7`FzqkU8k4gxtI=o z^P^oGFPm5jwZMI{;nH}$?p@uV8FT4r=|#GziKXK07bHJLtK}X%I0TON$uj(iJ`SY^ zc$b2CoxCQ>7LH@nxcdW&_C#fMYBtTxcg46dL{vf%EFCZ~eErMvZq&Z%Lhumnkn^4A zsx$ay(FnN7kYah}tZ@0?-0Niroa~13`?hVi6`ndno`G+E8;$<6^gsE-K3)TxyoJ4M zb6pj5=I8^FD5H@`^V#Qb2^0cx7wUz&cruA5g>6>qR5)O^t1(-qqP&1g=qvY#s&{bx zq8Hc%LsbK1*%n|Y=FfojpE;w~)G0-X4i*K3{o|J7`krhIOd*c*$y{WIKz2n2*EXEH zT{oml3Th5k*vkswuFXdGDlcLj15Nec5pFfZ*0?XHaF_lVuiB%Pv&p7z)%38}%$Gup zVTa~C8=cw%6BKn_|4E?bPNW4PT7}jZQLhDJhvf4z;~L)506IE0 zX!tWXX(QOQPRj-p80QG79t8T2^az4Zp2hOHziQlvT!|H)jv{Ixodabzv6lBj)6WRB z{)Kg@$~~(7$-az?lw$4@L%I&DI0Lo)PEJJziWP33a3azb?jyXt1v0N>2kxwA6b%l> zZqRpAo)Npi&loWbjFWtEV)783BbeIAhqyuc+~>i7aQ8shIXt)bjCWT6$~ro^>99G} z2XfmT0(|l!)XJb^E!#3z4oEGIsL(xd; zYX1`1I(cG|u#4R4T&C|m*9KB1`UzKvho5R@1eYtUL9B72{i(ir&ls8g!pD ztR|25xGaF!4z5M+U@@lQf(12?xGy`!|3E}7pI$k`jOIFjiDr{tqf0va&3pOn6Pu)% z@xtG2zjYuJXrV)DUrIF*y<1O1<$#54kZ#2;=X51J^F#0nZ0(;S$OZDt_U2bx{RZ=Q zMMdd$fH|!s{ zXq#l;{`xfV`gp&C>A`WrQU?d{!Ey5(1u*VLJt>i27aZ-^&2IIk=zP5p+{$q(K?2(b z8?9h)kvj9SF!Dr zoyF}?V|9;6abHxWk2cEvGs$-}Pg}D+ZzgkaN&$Snp%;5m%zh1E#?Wac-}x?BYlGN#U#Mek*}kek#I9XaHt?mz3*fDrRTQ#&#~xyeqJk1QJ~E$7qsw6 z?sV;|?*=-{M<1+hXoj?@-$y+(^BJ1H~wQ9G8C0#^aEAyhDduNX@haoa=PuPp zYsGv8UBfQaRHgBgLjmP^eh>fLMeh{8ic)?xz?#3kX-D#Z{;W#cd_`9OMFIaJg-=t`_3*!YDgtNQ2+QUEAJB9M{~AvT$H`E)IKmCR21H532+ata8_i_MR@ z2Xj<3w<`isF~Ah$W{|9;51ub*f4#9ziKrOR&jM{x7I_7()O@`F*5o$KtZ?fxU~g`t zUovNEVKYn$U~VX8eR)qb`7;D8pn*Pp$(otYTqL)5KH$lUS-jf}PGBjy$weoceAcPp z&5ZYB$r&P$MN{0H0AxCe4Qmd3T%M*5d4i%#!nmBCN-WU-4m4Tjxn-%j3HagwTxCZ9 z)j5vO-C7%s%D!&UfO>bi2oXiCw<-w{vVTK^rVbv#W=WjdADJy8$khnU!`ZWCIU`># zyjc^1W~pcu>@lDZ{zr6gv%)2X4n27~Ve+cQqcND%0?IFSP4sH#yIaXXYAq^z3|cg` z`I3$m%jra>e2W-=DiD@84T!cb%||k)nPmEE09NC%@PS_OLhkrX*U!cgD*;;&gIaA(DyVT4QD+q_xu z>r`tg{hiGY&DvD-)B*h+YEd+Zn)WylQl}<4>(_NlsKXCRV;a)Rcw!wtelM2_rWX`j zTh5A|i6=2BA(iMCnj_fob@*eA;V?oa4Z1kRBGaU07O70fb6-qmA$Hg$ps@^ka1=RO zTbE_2#)1bndC3VuK@e!Sftxq4=Uux}fDxXE#Q5_x=E1h>T5`DPHz zbH<_OjWx$wy7=%0!mo*qH*7N4tySm+R0~(rbus`7;+wGh;C0O%x~fEMkt!eV>U$`i z5>Q(o z=t$gPjgGh0&I7KY#k50V7DJRX<%^X z>6+ebc9efB3@eE2Tr){;?_w`vhgF>`-GDY(YkR{9RH(MiCnyRtd!LxXJ75z+?2 zGi@m^+2hKJ5sB1@Xi@s_@p_Kwbc<*LQ_`mr^Y%j}(sV_$`J(?_FWP)4NW*BIL~sR>t6 zM;qTJZ~GoY36&{h-Pf}L#y2UtR}>ZaI%A6VkU>vG4~}9^i$5WP2Tj?Cc}5oQxe2=q z8BeLa$hwCg_psjZyC2+?yX4*hJ58Wu^w9}}7X*+i5Rjqu5^@GzXiw#SUir1G1`jY% zOL=GE_ENYxhcyUrEt9XlMNP6kx6h&%6^u3@zB8KUCAa18T(R2J`%JjWZ z!{7cXaEW+Qu*iJPu+m>QqW}Lo$4Z+!I)0JNzZ&_M%=|B1yejFRM04bGAvu{=lNPd+ zJRI^DRQ(?FcVUD+bgEcAi@o(msqys9RTCG#)TjI!9~3-dc`>gW;HSJuQvH~d`MQs86R$|SKXHh zqS9Qy)u;T`>>a!$LuaE2keJV%;8g)tr&Nnc;EkvA-RanHXsy)D@XN0a>h}z2j81R; zsUNJf&g&rKpuD0WD@=dDrPHdBoK42WoBU|nMo17o(5^;M|dB4?|FsAGVrSyWcI`+FVw^vTVC`y}f(BwJl zrw3Sp151^9=}B})6@H*i4-dIN_o^br+BkcLa^H56|^2XsT0dESw2 zMX>(KqNl=x2K5=zIKg}2JpGAZu{I_IO}0$EQ5P{4zol**PCt3F4`GX}2@vr8#Y)~J zKb)gJeHcFnR@4SSh%b;c%J`l=W*40UPjF#q{<}ywv-=vHRFmDjv)NtmC zQx9qm)d%0zH&qG7AFa3VAU1S^(n8VFTC~Hb+HjYMjX8r#&_0MzlNR*mnLH5hi}`@{ zK$8qiDDvS_(L9_2vHgzEQ${DYSE;DqB!g*jhJghE&=LTnbgl&Xepo<*uRtV{2wDHN z)l;Kg$TA>Y|K8Lc&LjWGj<+bp4Hiye_@BfU(y#nF{fpR&|Ltbye?e^j0}8JC4#xi% zv29ZR%8%hk=3ZDvO-@1u8KmQ@6p%E|dlHuy#H1&MiC<*$YdLkHmR#F3ae;bKd;@*i z2_VfELG=B}JMLCO-6UQy^>RDE%K4b>c%9ki`f~Z2Qu8hO7C#t%Aeg8E%+}6P7Twtg z-)dj(w}_zFK&86KR@q9MHicUAucLVshUdmz_2@32(V`y3`&Kf8Q2I)+!n0mR=rrDU zXvv^$ho;yh*kNqJ#r1}b0|i|xRUF6;lhx$M*uG3SNLUTC@|htC z-=fsw^F%$qqz4%QdjBrS+ov}Qv!z00E+JWas>p?z@=t!WWU3K*?Z(0meTuTOC7OTx zU|kFLE0bLZ+WGcL$u4E}5dB0g`h|uwv3=H6f+{5z9oLv-=Q45+n~V4WwgO=CabjM% zBAN+RjM65(-}>Q2V#i1Na@a0`08g&y;W#@sBiX6Tpy8r}*+{RnyGUT`?XeHSqo#|J z^ww~c;ou|iyzpErDtlVU=`8N7JSu>4M z_pr9=tX0edVn9B}YFO2y(88j#S{w%E8vVOpAboK*27a7e4Ekjt0)hIX99*1oE;vex z7#%jhY=bPijA=Ce@9rRO(Vl_vnd00!^TAc<+wVvRM9{;hP*rqEL_(RzfK$er_^SN; z)1a8vo8~Dr5?;0X0J62Cusw$A*c^Sx1)dom`-)Pl7hsW4i(r*^Mw`z5K>!2ixB_mu z*Ddqjh}zceRFdmuX1akM1$3>G=#~|y?eYv(e-`Qy?bRHIq=fMaN~fB zUa6I8Rt=)jnplP>yuS+P&PxeWpJ#1$F`iqRl|jF$WL_aZFZl@kLo&d$VJtu&w?Q0O zzuXK>6gmygq(yXJy0C1SL}T8AplK|AGNUOhzlGeK_oo|haD@)5PxF}rV+5`-w{Aag zus45t=FU*{LguJ11Sr-28EZkq;!mJO7AQGih1L4rEyUmp>B!%X0YemsrV3QFvlgt* z5kwlPzaiJ+kZ^PMd-RRbl(Y?F*m`4*UIhIuf#8q>H_M=fM*L_Op-<_r zBZagV=4B|EW+KTja?srADTZXCd3Yv%^Chfpi)cg{ED${SI>InNpRj5!euKv?=Xn92 zsS&FH(*w`qLIy$doc>RE&A5R?u zzkl1sxX|{*fLpXvIW>9d<$ePROttn3oc6R!sN{&Y+>Jr@yeQN$sFR z;w6A<2-0%UA?c8Qf;sX7>>uKRBv3Ni)E9pI{uVzX|6Bb0U)`lhLE3hK58ivfRs1}d zNjlGK0hdq0qjV@q1qI%ZFMLgcpWSY~mB^LK)4GZ^h_@H+3?dAe_a~k*;9P_d7%NEFP6+ zgV(oGr*?W(ql?6SQ~`lUsjLb%MbfC4V$)1E0Y_b|OIYxz4?O|!kRb?BGrgiH5+(>s zoqM}v*;OBfg-D1l`M6T6{K`LG+0dJ1)!??G5g(2*vlNkm%Q(MPABT$r13q?|+kL4- zf)Mi5r$sn;u41aK(K#!m+goyd$c!KPl~-&-({j#D4^7hQkV3W|&>l_b!}!z?4($OA z5IrkfuT#F&S1(`?modY&I40%gtroig{YMvF{K{>5u^I51k8RriGd${z)=5k2tG zM|&Bp5kDTfb#vfuTTd?)a=>bX=lokw^y9+2LS?kwHQIWI~pYgy7 zb?A-RKVm_vM5!9?C%qYdfRAw& zAU7`up~%g=p@}pg#b7E)BFYx3g%(J36Nw(Dij!b>cMl@CSNbrW!DBDbTD4OXk!G4x zi}JBKc8HBYx$J~31PXH+4^x|UxK~(<@I;^3pWN$E=sYma@JP|8YL`L(zI6Y#c%Q{6 z*APf`DU$S4pr#_!60BH$FGViP14iJmbrzSrOkR;f3YZa{#E7Wpd@^4E-zH8EgPc-# zKWFPvh%WbqU_%ZEt`=Q?odKHc7@SUmY{GK`?40VuL~o)bS|is$Hn=<=KGHOsEC5tB zFb|q}gGlL97NUf$G$>^1b^3E18PZ~Pm9kX%*ftnolljiEt@2#F2R5ah$zbXd%V_Ev zyDd{1o_uuoBga$fB@Fw!V5F3jIr=a-ykqrK?WWZ#a(bglI_-8pq74RK*KfQ z0~Dzus7_l;pMJYf>Bk`)`S8gF!To-BdMnVw5M-pyu+aCiC5dwNH|6fgRsIKZcF&)g zr}1|?VOp}I3)IR@m1&HX1~#wsS!4iYqES zK}4J{Ei>;e3>LB#Oly>EZkW14^@YmpbgxCDi#0RgdM${&wxR+LiX}B+iRioOB0(pDKpVEI;ND?wNx>%e|m{RsqR_{(nmQ z3ZS}@t!p4a(BKx_-CYwrcyJ5u1TO9bcXti$8sy>xcLKqKCc#~UOZYD{llKTSFEjJ~ zyNWt>tLU}*>^`TvPxtP%F`ZJQw@W0^>x;!^@?k_)9#bF$j0)S3;mH-IR5y82l|%=F z2lR8zhP?XNP-ucZZ6A+o$xOyF!w;RaLHGh57GZ|TCXhJqY~GCh)aXEV$1O&$c}La1 zjuJxkY9SM4av^Hb;i7efiYaMwI%jGy`3NdY)+mcJhF(3XEiSlU3c|jMBi|;m-c?~T z+x0_@;SxcoY=(6xNgO$bBt~Pj8`-<1S|;Bsjrzw3@zSjt^JC3X3*$HI79i~!$RmTz zsblZsLYs7L$|=1CB$8qS!tXrWs!F@BVuh?kN(PvE5Av-*r^iYu+L^j^m9JG^#=m>@ z=1soa)H*w6KzoR$B8mBCXoU;f5^bVuwQ3~2LKg!yxomG1#XPmn(?YH@E~_ED+W6mxs%x{%Z<$pW`~ON1~2XjP5v(0{C{+6Dm$00tsd3w=f=ZENy zOgb-=f}|Hb*LQ$YdWg<(u7x3`PKF)B7ZfZ6;1FrNM63 z?O6tE%EiU@6%rVuwIQjvGtOofZBGZT1Sh(xLIYt9c4VI8`!=UJd2BfLjdRI#SbVAX ziT(f*RI^T!IL5Ac>ql7uduF#nuCRJ1)2bdvAyMxp-5^Ww5p#X{rb5)(X|fEhDHHW{ zw(Lfc$g;+Q`B0AiPGtmK%*aWfQQ$d!*U<|-@n2HZvCWSiw^I>#vh+LyC;aaVWGbmkENr z&kl*8o^_FW$T?rDYLO1Pyi%>@&kJKQoH2E0F`HjcN}Zlnx1ddoDA>G4Xu_jyp6vuT zPvC}pT&Owx+qB`zUeR|4G;OH(<<^_bzkjln0k40t`PQxc$7h(T8Ya~X+9gDc8Z9{Z z&y0RAU}#_kQGrM;__MK9vwIwK^aoqFhk~dK!ARf1zJqHMxF2?7-8|~yoO@_~Ed;_wvT%Vs{9RK$6uUQ|&@#6vyBsFK9eZW1Ft#D2)VpQRwpR(;x^ zdoTgMqfF9iBl%{`QDv7B0~8{8`8k`C4@cbZAXBu00v#kYl!#_Wug{)2PwD5cNp?K^ z9+|d-4z|gZ!L{57>!Ogfbzchm>J1)Y%?NThxIS8frAw@z>Zb9v%3_3~F@<=LG%r*U zaTov}{{^z~SeX!qgSYow`_5)ij*QtGp4lvF`aIGQ>@3ZTkDmsl#@^5*NGjOuu82}o zzLF~Q9SW+mP=>88%eSA1W4_W7-Q>rdq^?t=m6}^tDPaBRGFLg%ak93W!kOp#EO{6& zP%}Iff5HZQ9VW$~+9r=|Quj#z*=YwcnssS~9|ub2>v|u1JXP47vZ1&L1O%Z1DsOrDfSIMHU{VT>&>H=9}G3i@2rP+rx@eU@uE8rJNec zij~#FmuEBj03F1~ct@C@$>y)zB+tVyjV3*n`mtAhIM0$58vM9jOQC}JJOem|EpwqeMuYPxu3sv}oMS?S#o6GGK@8PN59)m&K4Dc&X% z(;XL_kKeYkafzS3Wn5DD>Yiw{LACy_#jY4op(>9q>>-*9@C0M+=b#bknAWZ37^(Ij zq>H%<@>o4a#6NydoF{_M4i4zB_KG)#PSye9bk0Ou8h%1Dtl7Q_y#7*n%g)?m>xF~( zjqvOwC;*qvN_3(*a+w2|ao0D?@okOvg8JskUw(l7n`0fncglavwKd?~l_ryKJ^Ky! zKCHkIC-o7%fFvPa$)YNh022lakMar^dgL=t#@XLyNHHw!b?%WlM)R@^!)I!smZL@k zBi=6wE5)2v&!UNV(&)oOYW(6Qa!nUjDKKBf-~Da=#^HE4(@mWk)LPvhyN3i4goB$3K8iV7uh zsv+a?#c4&NWeK(3AH;ETrMOIFgu{_@%XRwCZ;L=^8Ts)hix4Pf3yJRQ<8xb^CkdmC z?c_gB)XmRsk`9ch#tx4*hO=#qS7={~Vb4*tTf<5P%*-XMfUUYkI9T1cEF;ObfxxI-yNuA=I$dCtz3ey znVkctYD*`fUuZ(57+^B*R=Q}~{1z#2!ca?)+YsRQb+lt^LmEvZt_`=j^wqig+wz@n@ z`LIMQJT3bxMzuKg8EGBU+Q-6cs5(@5W?N>JpZL{$9VF)veF`L5%DSYTNQEypW%6$u zm_~}T{HeHj1bAlKl8ii92l9~$dm=UM21kLemA&b$;^!wB7#IKWGnF$TVq!!lBlG4 z{?Rjz?P(uvid+|i$VH?`-C&Gcb3{(~Vpg`w+O);Wk1|Mrjxrht0GfRUnZqz2MhrXa zqgVC9nemD5)H$to=~hp)c=l9?#~Z_7i~=U-`FZxb-|TR9@YCxx;Zjo-WpMNOn2)z) zFPGGVl%3N$f`gp$gPnWC+f4(rmts%fidpo^BJx72zAd7|*Xi{2VXmbOm)1`w^tm9% znM=0Fg4bDxH5PxPEm{P3#A(mxqlM7SIARP?|2&+c7qmU8kP&iApzL|F>Dz)Ixp_`O zP%xrP1M6@oYhgo$ZWwrAsYLa4 z|I;DAvJxno9HkQrhLPQk-8}=De{9U3U%)dJ$955?_AOms!9gia%)0E$Mp}$+0er@< zq7J&_SzvShM?e%V?_zUu{niL@gt5UFOjFJUJ}L?$f%eU%jUSoujr{^O=?=^{19`ON zlRIy8Uo_nqcPa6@yyz`CM?pMJ^^SN^Fqtt`GQ8Q#W4kE7`V9^LT}j#pMChl!j#g#J zr-=CCaV%xyFeQ9SK+mG(cTwW*)xa(eK;_Z(jy)woZp~> zA(4}-&VH+TEeLzPTqw&FOoK(ZjD~m{KW05fiGLe@E3Z2`rLukIDahE*`u!ubU)9`o zn^-lyht#E#-dt~S>}4y$-mSbR8{T@}22cn^refuQ08NjLOv?JiEWjyOnzk<^R5%gO zhUH_B{oz~u#IYwVnUg8?3P*#DqD8#X;%q%HY**=I>>-S|!X*-!x1{^l#OnR56O>iD zc;i;KS+t$koh)E3)w0OjWJl_aW2;xF=9D9Kr>)(5}4FqUbk# zI#$N8o0w;IChL49m9CJTzoC!|u{Ljd%ECgBOf$}&jA^$(V#P#~)`&g`H8E{uv52pp zwto`xUL-L&WTAVREEm$0g_gYPL(^vHq(*t1WCH_6alhkeW&GCZ3hL)|{O-jiFOBrF z!EW=Jej|dqQitT6!B-7&io2K)WIm~Q)v@yq%U|VpV+I?{y0@Yd%n8~-NuuM*pM~KA z85YB};IS~M(c<}4Hxx>qRK0cdl&e?t253N%vefkgds>Ubn8X}j6Vpgs>a#nFq$osY z1ZRwLqFv=+BTb=i%D2Wv>_yE0z}+niZ4?rE|*a3d7^kndWGwnFqt+iZ(7+aln<}jzbAQ(#Z2SS}3S$%Bd}^ zc9ghB%O)Z_mTZMRC&H#)I#fiLuIkGa^`4e~9oM5zKPx?zjkC&Xy0~r{;S?FS%c7w< zWbMpzc(xSw?9tGxG~_l}Acq}zjt5ClaB7-!vzqnlrX;}$#+PyQ9oU)_DfePh2E1<7 ztok6g6K^k^DuHR*iJ?jw?bs_whk|bx`dxu^nC6#e{1*m~z1eq7m}Cf$*^Eua(oi_I zAL+3opNhJteu&mWQ@kQWPucmiP)4|nFG`b2tpC;h{-PI@`+h?9v=9mn|0R-n8#t=+Z*FD(c5 zjj79Jxkgck*DV=wpFgRZuwr%}KTm+dx?RT@aUHJdaX-ODh~gByS?WGx&czAkvkg;x zrf92l8$Or_zOwJVwh>5rB`Q5_5}ef6DjS*$x30nZbuO3dijS*wvNEqTY5p1_A0gWr znH<(Qvb!os14|R)n2Ost>jS2;d1zyLHu`Svm|&dZD+PpP{Bh>U&`Md;gRl64q;>{8MJJM$?UNUd`aC>BiLe>*{ zJY15->yW+<3rLgYeTruFDtk1ovU<$(_y7#HgUq>)r0{^}Xbth}V#6?%5jeFYt;SG^ z3qF)=uWRU;Jj)Q}cpY8-H+l_n$2$6{ZR?&*IGr{>ek!69ZH0ZoJ*Ji+ezzlJ^%qL3 zO5a`6gwFw(moEzqxh=yJ9M1FTn!eo&qD#y5AZXErHs%22?A+JmS&GIolml!)rZTnUDM3YgzYfT#;OXn)`PWv3Ta z!-i|-Wojv*k&bC}_JJDjiAK(Ba|YZgUI{f}TdEOFT2+}nPmttytw7j%@bQZDV1vvj z^rp{gRkCDmYJHGrE1~e~AE!-&6B6`7UxVQuvRrfdFkGX8H~SNP_X4EodVd;lXd^>eV1jN+Tt4}Rsn)R0LxBz0c=NXU|pUe!MQQFkGBWbR3&(jLm z%RSLc#p}5_dO{GD=DEFr=Fc% z85CBF>*t!6ugI?soX(*JNxBp+-DdZ4X0LldiK}+WWGvXV(C(Ht|!3$psR=&c*HIM=BmX;pRIpz@Ale{9dhGe(U2|Giv;# zOc|;?p67J=Q(kamB*aus=|XP|m{jN^6@V*Bpm?ye56Njh#vyJqE=DweC;?Rv7faX~ zde03n^I~0B2vUmr;w^X37tVxUK?4}ifsSH5_kpKZIzpYu0;Kv}SBGfI2AKNp+VN#z`nI{UNDRbo-wqa4NEls zICRJpu)??cj^*WcZ^MAv+;bDbh~gpN$1Cor<{Y2oyIDws^JsfW^5AL$azE(T0p&pP z1Mv~6Q44R&RHoH95&OuGx2srIr<@zYJTOMKiVs;Bx3py89I87LOb@%mr`0)#;7_~Z zzcZj8?w=)>%5@HoCHE_&hnu(n_yQ-L(~VjpjjkbT7e)Dk5??fApg(d>vwLRJ-x{um z*Nt?DqTSxh_MIyogY!vf1mU1`Gld-&L)*43f6dilz`Q@HEz;+>MDDYv9u!s;WXeao zUq=TaL$P*IFgJzrGc>j1dDOd zed+=ZBo?w4mr$2)Ya}?vedDopomhW1`#P<%YOJ_j=WwClX0xJH-f@s?^tmzs_j7t!k zK@j^zS0Q|mM4tVP5Ram$VbS6|YDY&y?Q1r1joe9dj08#CM{RSMTU}(RCh`hp_Rkl- zGd|Cv~G@F{DLhCizAm9AN!^{rNs8hu!G@8RpnGx7e`-+K$ffN<0qjR zGq^$dj_Tv!n*?zOSyk5skI7JVKJ)3jysnjIu-@VSzQiP8r6MzudCU=~?v-U8yzo^7 zGf~SUTvEp+S*!X9uX!sq=o}lH;r{pzk~M*VA(uyQ`3C8!{C;)&6)95fv(cK!%Cuz$ z_Zal57H6kPN>25KNiI6z6F)jzEkh#%OqU#-__Xzy)KyH};81#N6OfX$$IXWzOn`Q& z4f$Z1t>)8&8PcYfEwY5UadU1yg+U*(1m2ZlHoC-!2?gB!!fLhmTl))D@dhvkx#+Yj z1O=LV{(T%{^IeCuFK>%QR!VZ4GnO5tK8a+thWE zg4VytZrwcS?7^ zuZfhYnB8dwd%VLO?DK7pV5Wi<(`~DYqOXn8#jUIL^)12*Dbhk4GmL_E2`WX&iT16o zk(t|hok(Y|v-wzn?4x34T)|+SfZP>fiq!><*%vnxGN~ypST-FtC+@TPv*vYv@iU!_ z@2gf|PrgQ?Ktf*9^CnJ(x*CtZVB8!OBfg0%!wL;Z8(tYYre0vcnPGlyCc$V(Ipl*P z_(J!a=o@vp^%Efme!K74(Ke7A>Y}|sxV+JL^aYa{~m%5#$$+R1? zGaQhZTTX!#s#=Xtpegqero$RNt&`4xn3g$)=y*;=N=Qai)}~`xtxI_N*#MMCIq#HFifT zz(-*m;pVH&+4bixL&Bbg)W5FN^bH87pAHp)zPkWNMfTFqS=l~AC$3FX3kQUSh_C?-ZftyClgM)o_D7cX$RGlEYblux0jv5 zTr|i-I3@ZPCGheCl~BGhImF)K4!9@?pC(gi3ozX=a!|r1)LFxy_8c&wY0<^{2cm|P zv6Y`QktY*;I)IUd5y3ne1CqpVanlY45z8hf4&$EUBnucDj16pDa4&GI&TArYhf*xh zdj>*%APH8(h~c>o@l#%T>R$e>rwVx_WUB|~V`p^JHsg*y12lzj&zF}w6W09HwB2yb z%Q~`es&(;7#*DUC_w-Dmt7|$*?TA_m;zB+-u{2;Bg{O}nV7G_@7~<)Bv8fH^G$XG8$(&{A zwXJK5LRK%M34(t$&NI~MHT{UQ9qN-V_yn|%PqC81EIiSzmMM=2zb`mIwiP_b)x+2M z7Gd`83h79j#SItpQ}luuf2uOU`my_rY5T{6P#BNlb%h%<#MZb=m@y5aW;#o1^2Z)SWo+b`y0gV^iRcZtz5!-05vF z7wNo=hc6h4hc&s@uL^jqRvD6thVYtbErDK9k!;+a0xoE0WL7zLixjn5;$fXvT=O3I zT6jI&^A7k6R{&5#lVjz#8%_RiAa2{di{`kx79K+j72$H(!ass|B%@l%KeeKchYLe_ z>!(JC2fxsv>XVen+Y42GeYPxMWqm`6F$(E<6^s|g(slNk!lL*6v^W2>f6hh^mE$s= z3D$)}{V5(Qm&A6bp%2Q}*GZ5Qrf}n7*Hr51?bJOyA-?B4vg6y_EX<*-e20h{=0Mxs zbuQGZ$fLyO5v$nQ&^kuH+mNq9O#MWSfThtH|0q1i!NrWj^S}_P;Q1OkYLW6U^?_7G zx2wg?CULj7))QU(n{$0JE%1t2dWrMi2g-Os{v|8^wK{@qlj%+1b^?NI z$}l2tjp0g>K3O+p%yK<9!XqmQ?E9>z&(|^Pi~aSRwI5x$jaA62GFz9%fmO3t3a>cq zK8Xbv=5Ps~4mKN5+Eqw12(!PEyedFXv~VLxMB~HwT1Vfo51pQ#D8e$e4pFZ{&RC2P z5gTIzl{3!&(tor^BwZfR8j4k{7Rq#`riKXP2O-Bh66#WWK2w=z;iD9GLl+3 zpHIaI4#lQ&S-xBK8PiQ%dwOh?%BO~DCo06pN7<^dnZCN@NzY{_Z1>rrB0U|nC&+!2 z2y!oBcTd2;@lzyk(B=TkyZ)zy0deK05*Q0zk+o$@nun`VI1Er7pjq>8V zNmlW{p7S^Btgb(TA}jL(uR>`0w8gHP^T~Sh5Tkip^spk4SBAhC{TZU}_Z)UJw-}zm zPq{KBm!k)?P{`-(9?LFt&YN4s%SIZ-9lJ!Ws~B%exHOeVFk3~}HewnnH(d)qkLQ_d z6h>O)pEE{vbOVw}E+jdYC^wM+AAhaI(YAibUc@B#_mDss0Ji&BK{WG`4 zOk>vSNq(Bq2IB@s>>Rxm6Wv?h;ZXkpb1l8u|+_qXWdC*jjcPCixq;!%BVPSp#hP zqo`%cNf&YoQXHC$D=D45RiT|5ngPlh?0T~?lUf*O)){K@*Kbh?3RW1j9-T?%lDk@y z4+~?wKI%Y!-=O|_IuKz|=)F;V7ps=5@g)RrE;;tvM$gUhG>jHcw2Hr@fS+k^Zr~>G z^JvPrZc}_&d_kEsqAEMTMJw!!CBw)u&ZVzmq+ZworuaE&TT>$pYsd9|g9O^0orAe8 z221?Va!l1|Y5X1Y?{G7rt1sX#qFA^?RLG^VjoxPf63;AS=_mVDfGJKg73L zsGdnTUD40y(>S##2l|W2Cy!H(@@5KBa(#gs`vlz}Y~$ot5VsqPQ{{YtjYFvIumZzt zA{CcxZLJR|4#{j7k~Tu*jkwz8QA|5G1$Cl895R`Zyp;irp1{KN){kB30O8P1W5;@bG znvX74roeMmQlUi=v9Y%(wl$ZC#9tKNFpvi3!C}f1m6Ct|l2g%psc{TJp)@yu)*e2> z((p0Fg*8gJ!|3WZke9;Z{8}&NRkv7iP=#_y-F}x^y?2m%-D_aj^)f04%mneyjo_;) z6qc_Zu$q37d~X``*eP~Q>I2gg%rrV8v=kDfpp$=%Vj}hF)^dsSWygoN(A$g*E=Do6FX?&(@F#7pbiJ`;c0c@Ul zDqW_90Wm#5f2L<(Lf3)3TeXtI7nhYwRm(F;*r_G6K@OPW4H(Y3O5SjUzBC}u3d|eQ8*8d@?;zUPE+i#QNMn=r(ap?2SH@vo*m z3HJ%XuG_S6;QbWy-l%qU;8x;>z>4pMW7>R}J%QLf%@1BY(4f_1iixd-6GlO7Vp*yU zp{VU^3?s?90i=!#>H`lxT!q8rk>W_$2~kbpz7eV{3wR|8E=8**5?qn8#n`*(bt1xRQrdGxyx2y%B$qmw#>ZV$c7%cO#%JM1lY$Y0q?Yuo> ze9KdJoiM)RH*SB%^;TAdX-zEjA7@%y=!0=Zg%iWK7jVI9b&Dk}0$Af&08KHo+ zOwDhFvA(E|ER%a^cdh@^wLUlmIv6?_3=BvX8jKk92L=Y}7Jf5OGMfh` zBdR1wFCi-i5@`9km{isRb0O%TX+f~)KNaEz{rXQa89`YIF;EN&gN)cigu6mNh>?Cm zAO&Im2flv6D{jwm+y<%WsPe4!89n~KN|7}Cb{Z;XweER73r}Qp2 zz}WP4j}U0&(uD&9yGy6`!+_v-S(yG*iytsTR#x_Rc>=6u^vnRDnf1gP{#2>`ffrAC% zTZ5WQ@hAK;P;>kX{D)mIXe4%a5p=LO1xXH@8T?mz7Q@d)$3pL{{B!2{-v70L*o1AO+|n5beiw~ zk@(>m?T3{2k2c;NWc^`4@P&Z?BjxXJ@;x1qhn)9Mn*IFdt_J-dIqx5#d`NfyfX~m( zIS~5)MfZ2Uy?_4W`47i}u0ZgPh<{D|w_d#;D}Q&U$Q-G}xM1A@1f{#%A$jh6Qp&0hQ<0bPOM z-{1Wm&p%%#eb_?x7i;bol EfAhh=DF6Tf diff --git a/task3/APP/.mvn/wrapper/maven-wrapper.properties b/task3/APP/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index 642d572..0000000 --- a/task3/APP/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,2 +0,0 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/task3/APP/Dockerfile b/task3/APP/Dockerfile deleted file mode 100644 index 22a3e37..0000000 --- a/task3/APP/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 -EXPOSE 8080 -WORKDIR /myapp -COPY src /myapp/src -COPY pom.xml /myapp/pom.xml -RUN mvn clean install - -FROM openjdk:8-jre-alpine as mvn_runner_stage2 -WORKDIR /myrunningapp -ARG hostname=saifnuaimii/app:v1.0.0 -EXPOSE 8080 -COPY --from=mvn_builder_stage1 /myapp . -CMD java -D -jar target/app-*.jar \ No newline at end of file diff --git a/task3/APP/local.dockerfile b/task3/APP/local.dockerfile deleted file mode 100644 index 22a3e37..0000000 --- a/task3/APP/local.dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 -EXPOSE 8080 -WORKDIR /myapp -COPY src /myapp/src -COPY pom.xml /myapp/pom.xml -RUN mvn clean install - -FROM openjdk:8-jre-alpine as mvn_runner_stage2 -WORKDIR /myrunningapp -ARG hostname=saifnuaimii/app:v1.0.0 -EXPOSE 8080 -COPY --from=mvn_builder_stage1 /myapp . -CMD java -D -jar target/app-*.jar \ No newline at end of file diff --git a/task3/APP/mvnw b/task3/APP/mvnw deleted file mode 100644 index a16b543..0000000 --- a/task3/APP/mvnw +++ /dev/null @@ -1,310 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Maven Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` - fi - # end of workaround - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -BASE_DIR=`find_maven_basedir "$(pwd)"` -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi -else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi - if [ -n "$MVNW_REPOURL" ]; then - jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - else - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; - esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" - if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` - fi - - if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget "$jarUrl" -O "$wrapperJarPath" - else - wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl -o "$wrapperJarPath" "$jarUrl" -f - else - curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f - fi - - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaClass=`cygpath --path --windows "$javaClass"` - fi - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") - fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR -fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/task3/APP/mvnw.cmd b/task3/APP/mvnw.cmd deleted file mode 100644 index c8d4337..0000000 --- a/task3/APP/mvnw.cmd +++ /dev/null @@ -1,182 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM https://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - -FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) -) -@REM End of extension - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% diff --git a/task3/APP/pom.xml b/task3/APP/pom.xml deleted file mode 100644 index 1029d36..0000000 --- a/task3/APP/pom.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.0.4.RELEASE - - - com.webapp - app - 1.0.0-RELEASE - APP - Demo project for Spring Boot - - - 1.8 - - - - - org.springframework.boot - spring-boot-starter-actuator - - - - org.springframework.boot - spring-boot-starter-data-mongodb - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/task3/APP/src/main/java/com/webapp/app/AppApplication.java b/task3/APP/src/main/java/com/webapp/app/AppApplication.java deleted file mode 100644 index 1b16909..0000000 --- a/task3/APP/src/main/java/com/webapp/app/AppApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.webapp.app; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class AppApplication { - - public static void main(String[] args) { - SpringApplication.run(AppApplication.class, args); - } - -} diff --git a/task3/APP/src/main/java/com/webapp/app/controller/CounterController.java b/task3/APP/src/main/java/com/webapp/app/controller/CounterController.java deleted file mode 100644 index 8beebdf..0000000 --- a/task3/APP/src/main/java/com/webapp/app/controller/CounterController.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.webapp.app.controller; - -import com.webapp.app.model.User; -import com.webapp.app.repository.MongoRepositoryImpl; -import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Optional; - - -@RestController -public class CounterController extends HttpServlet { - - private final MongoRepositoryImpl mongoRepository; - - private int iHitCounter; - - public CounterController(MongoRepositoryImpl mongoRepository) { - this.mongoRepository = mongoRepository; - iHitCounter = 0; - } - - @GetMapping("/") - public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { - String userAgent = request.getHeader("User-Agent"); - - String remoteAddr = request.getHeader("X-FORWARDED-FOR"); - - if (StringUtils.isEmpty(remoteAddr)) { - remoteAddr = request.getRemoteAddr(); - } - - User user = getUpdatedUserInfo(remoteAddr); - - response.setContentType("text/html"); - PrintWriter out = response.getWriter(); - - printStartHTMLTag(out); - out.println("
"); - out.println("Hello World !"); - out.print("
"); - out.println("

Welcome to my website !

"); - out.print("
"); - out.println("You are visitor number: " + (++iHitCounter)); - out.print("
"); - out.println("IP number: " + remoteAddr); - out.print("
"); - out.println("User Agent: " + userAgent); - out.print("
"); - out.println("count : " + user.getCount()); - out.println("
"); - printEndHTMLTag(out); - } - - private User getUpdatedUserInfo(String remoteAddr) { - Optional current = mongoRepository.findById(remoteAddr); - User user = new User(remoteAddr, 1); - current.ifPresent(u -> user.setCount(u.getCount() + 1)); - mongoRepository.save(user); - return user; - } - - private void printEndHTMLTag(PrintWriter out) { - out.println(""); - out.println(""); - } - - private void printStartHTMLTag(PrintWriter out) { - out.println(""); - out.println(""); - out.println("Hello World"); - out.println(""); - out.println(""); - } - -} \ No newline at end of file diff --git a/task3/APP/src/main/java/com/webapp/app/model/User.java b/task3/APP/src/main/java/com/webapp/app/model/User.java deleted file mode 100644 index 80ee670..0000000 --- a/task3/APP/src/main/java/com/webapp/app/model/User.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.webapp.app.model; - -import org.springframework.data.annotation.Id; - -import java.io.Serializable; - - -public class User implements Serializable { - - private static final long serialVersionUID = 1L; - - @Id - private String id; - private int count; - - public User(String id, int count) { - this.id = id; - this.count = count; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public int getCount() { - return count; - } - - public void setCount(int count) { - this.count = count; - } - - @Override - public String toString() { - return "User{" + - "id='" + id + '\'' + - ", count=" + count + - '}'; - } -} diff --git a/task3/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java b/task3/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java deleted file mode 100755 index 3d49eef..0000000 --- a/task3/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.webapp.app.repository; - - -import com.webapp.app.model.User; -import org.springframework.data.mongodb.repository.MongoRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface MongoRepositoryImpl extends MongoRepository { - - -} diff --git a/task3/APP/src/main/resources/application-dev.properties b/task3/APP/src/main/resources/application-dev.properties deleted file mode 100644 index a01bd52..0000000 --- a/task3/APP/src/main/resources/application-dev.properties +++ /dev/null @@ -1,2 +0,0 @@ -spring.data.mongodb.uri=mongodb://root:root@localhost:27017/ -spring.data.mongodb.database=users \ No newline at end of file diff --git a/task3/APP/src/main/resources/static/main.js b/task3/APP/src/main/resources/static/main.js deleted file mode 100755 index 08c6332..0000000 --- a/task3/APP/src/main/resources/static/main.js +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Create HTML table row. - * - * \param text (str) HTML code to be placed inside the row. - */ -function tr(text) { - return '' + text + ''; -} - -/** - * Create HTML table cell element. - * - * \param text (str) The text to be placed inside the cell. - */ -function td(text) { - return '' + text + ''; -} - -/** - * Edit value: load key and value into inputs. - */ -function editButton(key) { - var format = ''; - return format.replace(/{key}/g, key); -} - -/** - * Create HTML button with which a key-value pair is deleted from persistence. - * - * \param key (str) that'll be deleted when the created button is clicked. - */ -function deleteButton(key) { - var format = '' - return format.replace(/{key}/g, key); -} - -/** - * Create HTML table row element. - * - * \param key (str) text into the key cell. - * \param value (str) text into the value cell. - */ -function row(key, value) { - return $( - tr( - td(key) + - td(value) + - td(editButton(key)) + - td(deleteButton(key)))); -} - -/** - * Clear and reload the values in data table. - */ -function refreshTable() { - $.get('/values', function(data) { - var attr, - mainTable = $('#mainTable tbody'); - mainTable.empty(); - for (attr in data) { - if (data.hasOwnProperty(attr)) { - mainTable.append(row(attr, data[attr])); - } - } - }); -} - -function editKey(key) { - /* Find the row with key in first column (key column). */ - var format = '#mainTable tbody td:first-child:contains("{key}")', - selector = format.replace(/{key}/, key), - cells = $(selector).parent().children(), - key = cells[0].textContent, - value = cells[1].textContent, - keyInput = $('#keyInput'), - valueInput = $('#valueInput'); - - /* Load the key and value texts into inputs - * Select value text so it can be directly typed to - */ - keyInput.val(key); - valueInput.val(value); - valueInput.select(); -} - -/** - * Delete key-value pair. - * - * \param key (str) The key to be deleted. - */ -function deleteKey(key) { - /* - * Delete the key. - * Reload keys and values in table to reflect the deleted ones. - * Set keyboard focus to key input: ready to start typing. - */ - $.post('/delete', {key: key}, function() { - refreshTable(); - $('#keyInput').focus(); - }); -} - -$(document).ready(function() { - var keyInput = $('#keyInput'), - valueInput = $('#valueInput'); - - refreshTable(); - $('#addForm').on('submit', function(event) { - var data = { - key: keyInput.val(), - value: valueInput.val() - }; - - /* - * Persist the new key-value pair. - * Clear the inputs. - * Set keyboard focus to key input: ready to start typing. - */ - $.post('/add', data, function() { - refreshTable(); - keyInput.val(''); - valueInput.val(''); - keyInput.focus(); - }); - /* Prevent HTTP form submit */ - event.preventDefault(); - }); - - /* Focus keyboard input into key input; ready to start typing */ - keyInput.focus(); -}); diff --git a/task3/APP/src/main/resources/templates/index.html b/task3/APP/src/main/resources/templates/index.html deleted file mode 100755 index 9fdef13..0000000 --- a/task3/APP/src/main/resources/templates/index.html +++ /dev/null @@ -1,43 +0,0 @@ - - - -Intro to Redis with Spring Boot - - - - -
-
-
-
-
- - -
-
- - -
- -
-
-
- - - - - - - - - - -
KeyValue
-
-
-
- - - - - diff --git a/task3/APP/docker-compose.yml b/task3/docker-compose.yml similarity index 89% rename from task3/APP/docker-compose.yml rename to task3/docker-compose.yml index c205ef7..2ca7a68 100644 --- a/task3/APP/docker-compose.yml +++ b/task3/docker-compose.yml @@ -4,9 +4,7 @@ services: #-----------------web app----------------- webapp: - build: - context: . - dockerfile: local.dockerfile + image: saifnuaimii/app:v1.0.0 environment: - SPRING_DATA_MONGODB_URI=mongodb://root:root@mongo-db:27017/ - SPRING_DATA_MONGODB_DATABASE=users diff --git a/task3/start.sh b/task3/start.sh index 61394ae..4ba5185 100644 --- a/task3/start.sh +++ b/task3/start.sh @@ -1,4 +1,3 @@ #!/usr/bin/env bash -cd APP docker-compose up diff --git a/task4/APP/.gitignore b/task4/APP/.gitignore deleted file mode 100644 index a2a3040..0000000 --- a/task4/APP/.gitignore +++ /dev/null @@ -1,31 +0,0 @@ -HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/** -!**/src/test/** - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ - -### VS Code ### -.vscode/ diff --git a/task4/APP/.mvn/wrapper/MavenWrapperDownloader.java b/task4/APP/.mvn/wrapper/MavenWrapperDownloader.java deleted file mode 100644 index a45eb6b..0000000 --- a/task4/APP/.mvn/wrapper/MavenWrapperDownloader.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.net.*; -import java.io.*; -import java.nio.channels.*; -import java.util.Properties; - -public class MavenWrapperDownloader { - - private static final String WRAPPER_VERSION = "0.5.6"; - /** - * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. - */ - private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" - + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; - - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to - * use instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; - - /** - * Path where the maven-wrapper.jar will be saved to. - */ - private static final String MAVEN_WRAPPER_JAR_PATH = - ".mvn/wrapper/maven-wrapper.jar"; - - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; - - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); - - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if (mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); - } catch (IOException e) { - System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if (mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream.close(); - } - } catch (IOException e) { - // Ignore ... - } - } - } - System.out.println("- Downloading from: " + url); - - File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if (!outputFile.getParentFile().exists()) { - if (!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); - } - } - System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); - try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); - System.exit(1); - } - } - - private static void downloadFileFromURL(String urlString, File destination) throws Exception { - if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { - String username = System.getenv("MVNW_USERNAME"); - char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); - Authenticator.setDefault(new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(username, password); - } - }); - } - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(destination); - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); - } - -} diff --git a/task4/APP/.mvn/wrapper/maven-wrapper.jar b/task4/APP/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index 2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50710 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 EfAhh=DF6Tf diff --git a/task4/APP/.mvn/wrapper/maven-wrapper.properties b/task4/APP/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index 642d572..0000000 --- a/task4/APP/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,2 +0,0 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/task4/APP/Dockerfile b/task4/APP/Dockerfile deleted file mode 100644 index 22a3e37..0000000 --- a/task4/APP/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 -EXPOSE 8080 -WORKDIR /myapp -COPY src /myapp/src -COPY pom.xml /myapp/pom.xml -RUN mvn clean install - -FROM openjdk:8-jre-alpine as mvn_runner_stage2 -WORKDIR /myrunningapp -ARG hostname=saifnuaimii/app:v1.0.0 -EXPOSE 8080 -COPY --from=mvn_builder_stage1 /myapp . -CMD java -D -jar target/app-*.jar \ No newline at end of file diff --git a/task4/APP/ansible.yaml b/task4/APP/ansible.yaml deleted file mode 100644 index dda68dc..0000000 --- a/task4/APP/ansible.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -- hosts: localhost - tasks: - - name: mvn build - command: sh build.sh - - name: git clone repo - command: sh git-repo-save.sh - diff --git a/task4/APP/build.sh b/task4/APP/build.sh deleted file mode 100644 index 120873c..0000000 --- a/task4/APP/build.sh +++ /dev/null @@ -1,2 +0,0 @@ -apt-get install mvn -mvn clean install \ No newline at end of file diff --git a/task4/APP/docker-compose.yml b/task4/APP/docker-compose.yml deleted file mode 100644 index c205ef7..0000000 --- a/task4/APP/docker-compose.yml +++ /dev/null @@ -1,27 +0,0 @@ -version: '2' - -services: - - #-----------------web app----------------- - webapp: - build: - context: . - dockerfile: local.dockerfile - environment: - - SPRING_DATA_MONGODB_URI=mongodb://root:root@mongo-db:27017/ - - SPRING_DATA_MONGODB_DATABASE=users - ports: - - 8080:8080 - depends_on: - - mongo-db - - #-----------------mongo db----------------- - mongo-db: - restart: always - image: mongo - ports: - - "27017:27017" - environment: - - MONGO_INITDB_ROOT_USERNAME=root - - MONGO_INITDB_ROOT_PASSWORD=root - - MONGO_INITDB_DATABASE=users \ No newline at end of file diff --git a/task4/APP/git-repo-save.sh b/task4/APP/git-repo-save.sh deleted file mode 100644 index 0f49f59..0000000 --- a/task4/APP/git-repo-save.sh +++ /dev/null @@ -1 +0,0 @@ -git clone https://github.com/saifal-nuaimi/devops-task.git /opt/my_app/release_version \ No newline at end of file diff --git a/task4/APP/local.dockerfile b/task4/APP/local.dockerfile deleted file mode 100644 index ea13072..0000000 --- a/task4/APP/local.dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM maven:3.5.4-jdk-8-alpine as mvn_builder_stage1 -EXPOSE 8080 -WORKDIR /myapp -COPY src /myapp/src -COPY pom.xml /myapp/pom.xml -RUN mvn clean install - -FROM openjdk:8-jre-alpine as mvn_runner_stage2 -WORKDIR /myrunningapp -ARG hostname=saifnuaimii/app:v1.0.0 -EXPOSE 8080 -COPY --from=mvn_builder_stage1 /myapp . - -CMD java -D -jar target/app-*.jar \ No newline at end of file diff --git a/task4/APP/mvnw b/task4/APP/mvnw deleted file mode 100644 index a16b543..0000000 --- a/task4/APP/mvnw +++ /dev/null @@ -1,310 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Maven Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` - fi - # end of workaround - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -BASE_DIR=`find_maven_basedir "$(pwd)"` -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi -else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi - if [ -n "$MVNW_REPOURL" ]; then - jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - else - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; - esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" - if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` - fi - - if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget "$jarUrl" -O "$wrapperJarPath" - else - wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl -o "$wrapperJarPath" "$jarUrl" -f - else - curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f - fi - - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaClass=`cygpath --path --windows "$javaClass"` - fi - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") - fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR -fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/task4/APP/mvnw.cmd b/task4/APP/mvnw.cmd deleted file mode 100644 index c8d4337..0000000 --- a/task4/APP/mvnw.cmd +++ /dev/null @@ -1,182 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM https://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - -FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) -) -@REM End of extension - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% diff --git a/task4/APP/pom.xml b/task4/APP/pom.xml deleted file mode 100644 index 1029d36..0000000 --- a/task4/APP/pom.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.0.4.RELEASE - - - com.webapp - app - 1.0.0-RELEASE - APP - Demo project for Spring Boot - - - 1.8 - - - - - org.springframework.boot - spring-boot-starter-actuator - - - - org.springframework.boot - spring-boot-starter-data-mongodb - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/task4/APP/relase.txt b/task4/APP/relase.txt deleted file mode 100644 index 0ec25f7..0000000 --- a/task4/APP/relase.txt +++ /dev/null @@ -1 +0,0 @@ -v1.0.0 diff --git a/task4/APP/src/main/java/com/webapp/app/AppApplication.java b/task4/APP/src/main/java/com/webapp/app/AppApplication.java deleted file mode 100644 index 1b16909..0000000 --- a/task4/APP/src/main/java/com/webapp/app/AppApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.webapp.app; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class AppApplication { - - public static void main(String[] args) { - SpringApplication.run(AppApplication.class, args); - } - -} diff --git a/task4/APP/src/main/java/com/webapp/app/controller/CounterController.java b/task4/APP/src/main/java/com/webapp/app/controller/CounterController.java deleted file mode 100644 index 8beebdf..0000000 --- a/task4/APP/src/main/java/com/webapp/app/controller/CounterController.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.webapp.app.controller; - -import com.webapp.app.model.User; -import com.webapp.app.repository.MongoRepositoryImpl; -import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Optional; - - -@RestController -public class CounterController extends HttpServlet { - - private final MongoRepositoryImpl mongoRepository; - - private int iHitCounter; - - public CounterController(MongoRepositoryImpl mongoRepository) { - this.mongoRepository = mongoRepository; - iHitCounter = 0; - } - - @GetMapping("/") - public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { - String userAgent = request.getHeader("User-Agent"); - - String remoteAddr = request.getHeader("X-FORWARDED-FOR"); - - if (StringUtils.isEmpty(remoteAddr)) { - remoteAddr = request.getRemoteAddr(); - } - - User user = getUpdatedUserInfo(remoteAddr); - - response.setContentType("text/html"); - PrintWriter out = response.getWriter(); - - printStartHTMLTag(out); - out.println("
"); - out.println("Hello World !"); - out.print("
"); - out.println("

Welcome to my website !

"); - out.print("
"); - out.println("You are visitor number: " + (++iHitCounter)); - out.print("
"); - out.println("IP number: " + remoteAddr); - out.print("
"); - out.println("User Agent: " + userAgent); - out.print("
"); - out.println("count : " + user.getCount()); - out.println("
"); - printEndHTMLTag(out); - } - - private User getUpdatedUserInfo(String remoteAddr) { - Optional current = mongoRepository.findById(remoteAddr); - User user = new User(remoteAddr, 1); - current.ifPresent(u -> user.setCount(u.getCount() + 1)); - mongoRepository.save(user); - return user; - } - - private void printEndHTMLTag(PrintWriter out) { - out.println(""); - out.println(""); - } - - private void printStartHTMLTag(PrintWriter out) { - out.println(""); - out.println(""); - out.println("Hello World"); - out.println(""); - out.println(""); - } - -} \ No newline at end of file diff --git a/task4/APP/src/main/java/com/webapp/app/model/User.java b/task4/APP/src/main/java/com/webapp/app/model/User.java deleted file mode 100644 index 80ee670..0000000 --- a/task4/APP/src/main/java/com/webapp/app/model/User.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.webapp.app.model; - -import org.springframework.data.annotation.Id; - -import java.io.Serializable; - - -public class User implements Serializable { - - private static final long serialVersionUID = 1L; - - @Id - private String id; - private int count; - - public User(String id, int count) { - this.id = id; - this.count = count; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public int getCount() { - return count; - } - - public void setCount(int count) { - this.count = count; - } - - @Override - public String toString() { - return "User{" + - "id='" + id + '\'' + - ", count=" + count + - '}'; - } -} diff --git a/task4/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java b/task4/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java deleted file mode 100755 index 3d49eef..0000000 --- a/task4/APP/src/main/java/com/webapp/app/repository/MongoRepositoryImpl.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.webapp.app.repository; - - -import com.webapp.app.model.User; -import org.springframework.data.mongodb.repository.MongoRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface MongoRepositoryImpl extends MongoRepository { - - -} diff --git a/task4/APP/src/main/resources/application-dev.properties b/task4/APP/src/main/resources/application-dev.properties deleted file mode 100644 index a01bd52..0000000 --- a/task4/APP/src/main/resources/application-dev.properties +++ /dev/null @@ -1,2 +0,0 @@ -spring.data.mongodb.uri=mongodb://root:root@localhost:27017/ -spring.data.mongodb.database=users \ No newline at end of file diff --git a/task4/APP/src/main/resources/static/main.js b/task4/APP/src/main/resources/static/main.js deleted file mode 100755 index 08c6332..0000000 --- a/task4/APP/src/main/resources/static/main.js +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Create HTML table row. - * - * \param text (str) HTML code to be placed inside the row. - */ -function tr(text) { - return '' + text + ''; -} - -/** - * Create HTML table cell element. - * - * \param text (str) The text to be placed inside the cell. - */ -function td(text) { - return '' + text + ''; -} - -/** - * Edit value: load key and value into inputs. - */ -function editButton(key) { - var format = ''; - return format.replace(/{key}/g, key); -} - -/** - * Create HTML button with which a key-value pair is deleted from persistence. - * - * \param key (str) that'll be deleted when the created button is clicked. - */ -function deleteButton(key) { - var format = '' - return format.replace(/{key}/g, key); -} - -/** - * Create HTML table row element. - * - * \param key (str) text into the key cell. - * \param value (str) text into the value cell. - */ -function row(key, value) { - return $( - tr( - td(key) + - td(value) + - td(editButton(key)) + - td(deleteButton(key)))); -} - -/** - * Clear and reload the values in data table. - */ -function refreshTable() { - $.get('/values', function(data) { - var attr, - mainTable = $('#mainTable tbody'); - mainTable.empty(); - for (attr in data) { - if (data.hasOwnProperty(attr)) { - mainTable.append(row(attr, data[attr])); - } - } - }); -} - -function editKey(key) { - /* Find the row with key in first column (key column). */ - var format = '#mainTable tbody td:first-child:contains("{key}")', - selector = format.replace(/{key}/, key), - cells = $(selector).parent().children(), - key = cells[0].textContent, - value = cells[1].textContent, - keyInput = $('#keyInput'), - valueInput = $('#valueInput'); - - /* Load the key and value texts into inputs - * Select value text so it can be directly typed to - */ - keyInput.val(key); - valueInput.val(value); - valueInput.select(); -} - -/** - * Delete key-value pair. - * - * \param key (str) The key to be deleted. - */ -function deleteKey(key) { - /* - * Delete the key. - * Reload keys and values in table to reflect the deleted ones. - * Set keyboard focus to key input: ready to start typing. - */ - $.post('/delete', {key: key}, function() { - refreshTable(); - $('#keyInput').focus(); - }); -} - -$(document).ready(function() { - var keyInput = $('#keyInput'), - valueInput = $('#valueInput'); - - refreshTable(); - $('#addForm').on('submit', function(event) { - var data = { - key: keyInput.val(), - value: valueInput.val() - }; - - /* - * Persist the new key-value pair. - * Clear the inputs. - * Set keyboard focus to key input: ready to start typing. - */ - $.post('/add', data, function() { - refreshTable(); - keyInput.val(''); - valueInput.val(''); - keyInput.focus(); - }); - /* Prevent HTTP form submit */ - event.preventDefault(); - }); - - /* Focus keyboard input into key input; ready to start typing */ - keyInput.focus(); -}); diff --git a/task4/APP/src/main/resources/templates/index.html b/task4/APP/src/main/resources/templates/index.html deleted file mode 100755 index 9fdef13..0000000 --- a/task4/APP/src/main/resources/templates/index.html +++ /dev/null @@ -1,43 +0,0 @@ - - - -Intro to Redis with Spring Boot - - - - -
-
-
-
-
- - -
-
- - -
- -
-
-
- - - - - - - - - - -
KeyValue
-
-
-
- - - - - diff --git a/task5/deploy.sh b/task5/deploy.sh index fcdca6c..c45dd09 100755 --- a/task5/deploy.sh +++ b/task5/deploy.sh @@ -1,11 +1,11 @@ #!/bin/bash -cd APP-helm +cd helm helm upgrade \ --install \ --wait \ --force \ --timeout 900 \ - --tiller-namespace=""\ - --namespace="" \ + --tiller-namespace=kryptonite\ + --namespace=testingmy \ my-app \ . diff --git a/task5/APP-helm/Chart.yaml b/task5/helm/Chart.yaml similarity index 100% rename from task5/APP-helm/Chart.yaml rename to task5/helm/Chart.yaml diff --git a/task5/APP-helm/app.properties b/task5/helm/app.properties similarity index 100% rename from task5/APP-helm/app.properties rename to task5/helm/app.properties diff --git a/task5/APP-helm/charts/mongodb-4.8.4.tgz b/task5/helm/charts/mongodb-4.8.4.tgz similarity index 100% rename from task5/APP-helm/charts/mongodb-4.8.4.tgz rename to task5/helm/charts/mongodb-4.8.4.tgz diff --git a/task5/APP-helm/requirements.lock b/task5/helm/requirements.lock similarity index 100% rename from task5/APP-helm/requirements.lock rename to task5/helm/requirements.lock diff --git a/task5/APP-helm/requirements.yaml b/task5/helm/requirements.yaml similarity index 100% rename from task5/APP-helm/requirements.yaml rename to task5/helm/requirements.yaml diff --git a/task5/APP-helm/templates/configmap.yaml b/task5/helm/templates/configmap.yaml similarity index 100% rename from task5/APP-helm/templates/configmap.yaml rename to task5/helm/templates/configmap.yaml diff --git a/task5/APP-helm/templates/deployment.yaml b/task5/helm/templates/deployment.yaml similarity index 100% rename from task5/APP-helm/templates/deployment.yaml rename to task5/helm/templates/deployment.yaml diff --git a/task5/APP-helm/templates/service.yaml b/task5/helm/templates/service.yaml similarity index 100% rename from task5/APP-helm/templates/service.yaml rename to task5/helm/templates/service.yaml diff --git a/task5/APP-helm/values.yaml b/task5/helm/values.yaml similarity index 100% rename from task5/APP-helm/values.yaml rename to task5/helm/values.yaml From b8459fd5d54972dc227ac7fa339d0c7fd25e54d1 Mon Sep 17 00:00:00 2001 From: Saif Al Nuaimi Date: Tue, 3 Mar 2020 00:13:10 +0200 Subject: [PATCH 13/18] fix: fix notes --- task2/scripts/builder.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/task2/scripts/builder.sh b/task2/scripts/builder.sh index 8feac83..3132b29 100755 --- a/task2/scripts/builder.sh +++ b/task2/scripts/builder.sh @@ -64,7 +64,8 @@ is_healthy() { while [ "$health_status" = "running" ] do echo "container is running" - sleep 10 + echo "wait for the service to be up" + sleep 7 break done } @@ -77,7 +78,7 @@ APP_STATUS=$(curl --silent http://localhost:8080/actuator/health | jq .status) echo $APP_STATUS if [ $APP_STATUS="UP" ]; then echo "Your application working fine" -else [$APP_STATUS="Down"] +else [ $APP_STATUS="Down" ] echo "missing/wrong .Application failed in testing" exit 5 fi From 9b637ff08abe555348164fe96dfa9db160c6b1f3 Mon Sep 17 00:00:00 2001 From: Saif Al Nuaimi Date: Tue, 3 Mar 2020 00:13:49 +0200 Subject: [PATCH 14/18] fix: fix notes --- task5/deploy.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/task5/deploy.sh b/task5/deploy.sh index c45dd09..862286a 100755 --- a/task5/deploy.sh +++ b/task5/deploy.sh @@ -5,7 +5,7 @@ helm upgrade \ --wait \ --force \ --timeout 900 \ - --tiller-namespace=kryptonite\ - --namespace=testingmy \ + --tiller-namespace=""\ + --namespace="" \ my-app \ . From 7afe6712c7dbbb06ddb4e8de753615b5c6989b99 Mon Sep 17 00:00:00 2001 From: Saif Al Nuaimi Date: Tue, 3 Mar 2020 00:17:25 +0200 Subject: [PATCH 15/18] fix: fix notes --- task5/helm/templates/deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/task5/helm/templates/deployment.yaml b/task5/helm/templates/deployment.yaml index 71c0711..dad4c09 100644 --- a/task5/helm/templates/deployment.yaml +++ b/task5/helm/templates/deployment.yaml @@ -28,7 +28,7 @@ spec: name: {{ tpl ( .Values.app.env.config.name ) .}} ports: - name: http - containerPort: {{ .Values.service.internalPort }} + containerPort: 8080 protocol: TCP livenessProbe: httpGet: From 1993db512ed1ec1895b022f5fa5241d580cabc4d Mon Sep 17 00:00:00 2001 From: Saif Al Nuaimi Date: Tue, 3 Mar 2020 00:46:08 +0200 Subject: [PATCH 16/18] fix: fix notes --- task4/roles/docker/tasks/docker-compose.yml | 21 +++++++++++++++++++++ task4/roles/docker/tasks/docker-users.yml | 7 +++++++ task4/sites.yml | 14 ++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 task4/roles/docker/tasks/docker-compose.yml create mode 100644 task4/roles/docker/tasks/docker-users.yml create mode 100644 task4/sites.yml diff --git a/task4/roles/docker/tasks/docker-compose.yml b/task4/roles/docker/tasks/docker-compose.yml new file mode 100644 index 0000000..d1c356e --- /dev/null +++ b/task4/roles/docker/tasks/docker-compose.yml @@ -0,0 +1,21 @@ +--- +- name: Check current docker-compose version. + command: docker-compose --version + register: docker_compose_current_version + changed_when: false + failed_when: false + +- name: Delete existing docker-compose version if it's different. + file: + path: "{{ docker_compose_path }}" + state: absent + when: > + docker_compose_current_version.stdout is defined + and docker_compose_version not in docker_compose_current_version.stdout + +- name: Install Docker Compose (if configured). + become: yes + get_url: + url: https://github.com/docker/compose/releases/download/{{ docker_compose_version }}/docker-compose-Linux-x86_64 + dest: "{{ docker_compose_path }}" + mode: 0755 diff --git a/task4/roles/docker/tasks/docker-users.yml b/task4/roles/docker/tasks/docker-users.yml new file mode 100644 index 0000000..b3b6e0f --- /dev/null +++ b/task4/roles/docker/tasks/docker-users.yml @@ -0,0 +1,7 @@ +--- +- name: Ensure docker users are added to the docker group. + user: + name: "{{ item }}" + groups: docker + append: true + with_items: "{{ docker_users }}" diff --git a/task4/sites.yml b/task4/sites.yml new file mode 100644 index 0000000..a4f9a40 --- /dev/null +++ b/task4/sites.yml @@ -0,0 +1,14 @@ +- hosts: all + gather_facts: False + +- hosts: all + connection: local + become: yes + become_user: root + roles: + - openjdk8 + - ansible + - maven + + - docker + - kubectl From 80f1ba2eb85d579ba94dcfef06a002f272d4d888 Mon Sep 17 00:00:00 2001 From: Saif Al Nuaimi Date: Tue, 3 Mar 2020 00:46:51 +0200 Subject: [PATCH 17/18] fix: fix notes --- task4/roles/docker/defaults/main.yml | 2 + task4/roles/docker/handlers/main.yml | 2 + task4/roles/docker/tasks/main.yml | 23 +++++++++ task4/roles/docker/tests/inventory | 2 + task4/roles/docker/tests/test.yml | 5 ++ task4/roles/docker/vars/main.yml | 2 + task4/roles/kubectl/defaults/main.yml | 65 ++++++++++++++++++++++++++ task4/roles/kubectl/handlers/main.yml | 2 + task4/roles/kubectl/tasks/main.yml | 2 + task4/roles/kubectl/tests/inventory | 2 + task4/roles/kubectl/tests/test.yml | 5 ++ task4/roles/kubectl/vars/main.yml | 2 + task4/roles/maven/defaults/main.yml | 2 + task4/roles/maven/handlers/main.yml | 2 + task4/roles/maven/tasks/main.yml | 11 +++++ task4/roles/maven/tests/inventory | 2 + task4/roles/maven/tests/test.yml | 5 ++ task4/roles/maven/vars/main.yml | 2 + task4/roles/openjdk8/defaults/main.yml | 2 + task4/roles/openjdk8/handlers/main.yml | 2 + task4/roles/openjdk8/tasks/main.yml | 11 +++++ task4/roles/openjdk8/tests/inventory | 2 + task4/roles/openjdk8/tests/test.yml | 5 ++ task4/roles/openjdk8/vars/main.yml | 2 + 24 files changed, 162 insertions(+) create mode 100644 task4/roles/docker/defaults/main.yml create mode 100644 task4/roles/docker/handlers/main.yml create mode 100644 task4/roles/docker/tasks/main.yml create mode 100644 task4/roles/docker/tests/inventory create mode 100644 task4/roles/docker/tests/test.yml create mode 100644 task4/roles/docker/vars/main.yml create mode 100644 task4/roles/kubectl/defaults/main.yml create mode 100644 task4/roles/kubectl/handlers/main.yml create mode 100644 task4/roles/kubectl/tasks/main.yml create mode 100644 task4/roles/kubectl/tests/inventory create mode 100644 task4/roles/kubectl/tests/test.yml create mode 100644 task4/roles/kubectl/vars/main.yml create mode 100644 task4/roles/maven/defaults/main.yml create mode 100644 task4/roles/maven/handlers/main.yml create mode 100644 task4/roles/maven/tasks/main.yml create mode 100644 task4/roles/maven/tests/inventory create mode 100644 task4/roles/maven/tests/test.yml create mode 100644 task4/roles/maven/vars/main.yml create mode 100644 task4/roles/openjdk8/defaults/main.yml create mode 100644 task4/roles/openjdk8/handlers/main.yml create mode 100644 task4/roles/openjdk8/tasks/main.yml create mode 100644 task4/roles/openjdk8/tests/inventory create mode 100644 task4/roles/openjdk8/tests/test.yml create mode 100644 task4/roles/openjdk8/vars/main.yml diff --git a/task4/roles/docker/defaults/main.yml b/task4/roles/docker/defaults/main.yml new file mode 100644 index 0000000..c456773 --- /dev/null +++ b/task4/roles/docker/defaults/main.yml @@ -0,0 +1,2 @@ +--- +# defaults file for docker \ No newline at end of file diff --git a/task4/roles/docker/handlers/main.yml b/task4/roles/docker/handlers/main.yml new file mode 100644 index 0000000..85610a1 --- /dev/null +++ b/task4/roles/docker/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for docker \ No newline at end of file diff --git a/task4/roles/docker/tasks/main.yml b/task4/roles/docker/tasks/main.yml new file mode 100644 index 0000000..6d42e04 --- /dev/null +++ b/task4/roles/docker/tasks/main.yml @@ -0,0 +1,23 @@ +--- +- name: Install Docker. + become: yes + package: + name: "{{ docker_package }}" + state: "{{ docker_package_state }}" + notify: restart docker + +- name: Ensure Docker is started and enabled at boot. + become: yes + service: + name: docker + state: "{{ docker_service_state }}" + enabled: "{{ docker_service_enabled }}" + +- name: Ensure handlers are notified now to avoid firewall conflicts. + meta: flush_handlers + +- include_tasks: docker-compose.yml + when: docker_install_compose + +- include_tasks: docker-users.yml + when: docker_users diff --git a/task4/roles/docker/tests/inventory b/task4/roles/docker/tests/inventory new file mode 100644 index 0000000..878877b --- /dev/null +++ b/task4/roles/docker/tests/inventory @@ -0,0 +1,2 @@ +localhost + diff --git a/task4/roles/docker/tests/test.yml b/task4/roles/docker/tests/test.yml new file mode 100644 index 0000000..2c81ca4 --- /dev/null +++ b/task4/roles/docker/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + remote_user: root + roles: + - docker \ No newline at end of file diff --git a/task4/roles/docker/vars/main.yml b/task4/roles/docker/vars/main.yml new file mode 100644 index 0000000..dc934ce --- /dev/null +++ b/task4/roles/docker/vars/main.yml @@ -0,0 +1,2 @@ +--- +# vars file for docker \ No newline at end of file diff --git a/task4/roles/kubectl/defaults/main.yml b/task4/roles/kubectl/defaults/main.yml new file mode 100644 index 0000000..7fc427e --- /dev/null +++ b/task4/roles/kubectl/defaults/main.yml @@ -0,0 +1,65 @@ +--- +- name: kubectl + block: + + ############################################################################ + # Install ctl + ############################################################################ + - name: install pre-requisits + apt: + name: "{{item}}" + state: present + with_items: + - ca-certificates + - apt-transport-https + + - name: Add repo key + apt_key: + url: "https://packages.cloud.google.com/apt/doc/apt-key.gpg" + state: present + + - name: Add kubernetes repo + apt_repository: + repo: 'deb http://apt.kubernetes.io/ kubernetes-xenial main' + state: present + update_cache: yes + + - name: Install kubectl packages + apt: + name: "{{item}}" + state: present + with_items: + - kubectl + + - name: kubectl autocomplete + command: 'echo "source <(kubectl completion bash)" >> ~/.bashrc' + + ############################################################################ + # Install HELM + ############################################################################ + - name: Check if installed + stat: + path: /usr/local/bin/helm + register: installed_helm + + - when: installed_helm.stat.exists == False + block: + + - name: download & extract helm + unarchive: + src: https://kubernetes-helm.storage.googleapis.com/helm-v2.11.0-linux-amd64.tar.gz + dest: /usr/local/bin + remote_src: yes + + - name: move file + command: mv /usr/local/bin/linux-amd64/helm /usr/local/bin/helm + + - name: set file attribute + file: + path: /usr/local/bin/helm + mode: 0755 + + become: yes + +- name: kubectl autocomplete + command: 'echo "source <(kubectl completion bash)" >> ~/.bashrc' diff --git a/task4/roles/kubectl/handlers/main.yml b/task4/roles/kubectl/handlers/main.yml new file mode 100644 index 0000000..8358b4e --- /dev/null +++ b/task4/roles/kubectl/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for kubectl \ No newline at end of file diff --git a/task4/roles/kubectl/tasks/main.yml b/task4/roles/kubectl/tasks/main.yml new file mode 100644 index 0000000..710965f --- /dev/null +++ b/task4/roles/kubectl/tasks/main.yml @@ -0,0 +1,2 @@ +--- +# tasks file for kubectl \ No newline at end of file diff --git a/task4/roles/kubectl/tests/inventory b/task4/roles/kubectl/tests/inventory new file mode 100644 index 0000000..878877b --- /dev/null +++ b/task4/roles/kubectl/tests/inventory @@ -0,0 +1,2 @@ +localhost + diff --git a/task4/roles/kubectl/tests/test.yml b/task4/roles/kubectl/tests/test.yml new file mode 100644 index 0000000..4ec6947 --- /dev/null +++ b/task4/roles/kubectl/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + remote_user: root + roles: + - kubectl \ No newline at end of file diff --git a/task4/roles/kubectl/vars/main.yml b/task4/roles/kubectl/vars/main.yml new file mode 100644 index 0000000..bb523ae --- /dev/null +++ b/task4/roles/kubectl/vars/main.yml @@ -0,0 +1,2 @@ +--- +# vars file for kubectl \ No newline at end of file diff --git a/task4/roles/maven/defaults/main.yml b/task4/roles/maven/defaults/main.yml new file mode 100644 index 0000000..e886af2 --- /dev/null +++ b/task4/roles/maven/defaults/main.yml @@ -0,0 +1,2 @@ +--- +# defaults file for maven \ No newline at end of file diff --git a/task4/roles/maven/handlers/main.yml b/task4/roles/maven/handlers/main.yml new file mode 100644 index 0000000..a327a40 --- /dev/null +++ b/task4/roles/maven/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for maven \ No newline at end of file diff --git a/task4/roles/maven/tasks/main.yml b/task4/roles/maven/tasks/main.yml new file mode 100644 index 0000000..9730770 --- /dev/null +++ b/task4/roles/maven/tasks/main.yml @@ -0,0 +1,11 @@ +--- +- name: maven + block: + + - name: Install Maven + apt: + name: "maven" + state: present + update_cache: yes + + become: yes \ No newline at end of file diff --git a/task4/roles/maven/tests/inventory b/task4/roles/maven/tests/inventory new file mode 100644 index 0000000..878877b --- /dev/null +++ b/task4/roles/maven/tests/inventory @@ -0,0 +1,2 @@ +localhost + diff --git a/task4/roles/maven/tests/test.yml b/task4/roles/maven/tests/test.yml new file mode 100644 index 0000000..f36f542 --- /dev/null +++ b/task4/roles/maven/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + remote_user: root + roles: + - maven \ No newline at end of file diff --git a/task4/roles/maven/vars/main.yml b/task4/roles/maven/vars/main.yml new file mode 100644 index 0000000..4bdb881 --- /dev/null +++ b/task4/roles/maven/vars/main.yml @@ -0,0 +1,2 @@ +--- +# vars file for maven \ No newline at end of file diff --git a/task4/roles/openjdk8/defaults/main.yml b/task4/roles/openjdk8/defaults/main.yml new file mode 100644 index 0000000..c886014 --- /dev/null +++ b/task4/roles/openjdk8/defaults/main.yml @@ -0,0 +1,2 @@ +--- +# defaults file for openjdk8 \ No newline at end of file diff --git a/task4/roles/openjdk8/handlers/main.yml b/task4/roles/openjdk8/handlers/main.yml new file mode 100644 index 0000000..e35009f --- /dev/null +++ b/task4/roles/openjdk8/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for openjdk8 \ No newline at end of file diff --git a/task4/roles/openjdk8/tasks/main.yml b/task4/roles/openjdk8/tasks/main.yml new file mode 100644 index 0000000..1dfc684 --- /dev/null +++ b/task4/roles/openjdk8/tasks/main.yml @@ -0,0 +1,11 @@ +--- +- name: openjdk8 + block: + + - name: Install OpenJDK 8 + apt: + name: "openjdk-8-jdk" + state: present + update_cache: yes + + become: yes diff --git a/task4/roles/openjdk8/tests/inventory b/task4/roles/openjdk8/tests/inventory new file mode 100644 index 0000000..878877b --- /dev/null +++ b/task4/roles/openjdk8/tests/inventory @@ -0,0 +1,2 @@ +localhost + diff --git a/task4/roles/openjdk8/tests/test.yml b/task4/roles/openjdk8/tests/test.yml new file mode 100644 index 0000000..ba76023 --- /dev/null +++ b/task4/roles/openjdk8/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + remote_user: root + roles: + - openjdk8 \ No newline at end of file diff --git a/task4/roles/openjdk8/vars/main.yml b/task4/roles/openjdk8/vars/main.yml new file mode 100644 index 0000000..ac605c8 --- /dev/null +++ b/task4/roles/openjdk8/vars/main.yml @@ -0,0 +1,2 @@ +--- +# vars file for openjdk8 \ No newline at end of file From c2ddcf891a089cbc9a2d51c739e87813c582691e Mon Sep 17 00:00:00 2001 From: Saif Al Nuaimi Date: Tue, 3 Mar 2020 22:39:00 +0200 Subject: [PATCH 18/18] fix: fix notes --- task2/scripts/builder.sh | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/task2/scripts/builder.sh b/task2/scripts/builder.sh index 3132b29..7097055 100755 --- a/task2/scripts/builder.sh +++ b/task2/scripts/builder.sh @@ -65,7 +65,7 @@ is_healthy() { do echo "container is running" echo "wait for the service to be up" - sleep 7 + sleep 10 break done } @@ -88,13 +88,12 @@ docker-compose down #----------------------------------------------------- # ## Push the docker Image -docker_push_image() { - docker push $DOCKER_IMAGE -} -if docker_push_image; then +CHECK_IMAGE_EXISTS=$(docker images | grep $DOCKER_IMAGE | awk '{print $1}' | head -n 1 ) +echo $CHECK_IMAGE_EXISTS +if [ $CHECK_IMAGE_EXISTS=$DOCKER_IMAGE ] ; then echo "Pushing your image" - docker_push_image + docker push $DOCKER_IMAGE else echo "missing/wrong .Pusing failed." exit 6