diff --git a/DockerFile b/DockerFile new file mode 100644 index 000000000..72e202946 --- /dev/null +++ b/DockerFile @@ -0,0 +1,45 @@ +# Base Image +FROM gradle:7.3.3-jdk11 AS builder + +# Working directory +WORKDIR /desktop_app + +# Copy all project files +COPY . . + +# Make gradlew executable +RUN chmod +x ./gradlew + +# Build application +RUN ./gradlew assemble --no-daemon + +# Check where the JAR file actually is +RUN find /desktop_app -name "*.jar" | grep -v "/gradle/" > jar_location.txt + +# Runtime stage - JRE only +FROM eclipse-temurin:11-jre-jammy +WORKDIR /app + +# Create non-root user (increased security) +RUN groupadd -r appgroup && \ + useradd -r -g appgroup -d /app -s /bin/false appuser && \ + chown -R appuser:appgroup /app + +# Copy the file with JAR location +COPY --from=builder /desktop_app/jar_location.txt /app/ + +COPY --from=builder /desktop_app/build/ /app/build/ + +# Use the first JAR found in the build directory (temp solution) +RUN find /app/build -name "*.jar" | grep -v "/gradle/" | head -1 > jar_path.txt && \ + if [ -s jar_path.txt ]; then \ + cp $(cat jar_path.txt) /app/app.jar && \ + chown appuser:appgroup /app/app.jar; \ + else \ + echo "No JAR files found!" && exit 1; \ + fi + +# Runtime config +USER appuser +EXPOSE 8080 +ENTRYPOINT ["java", "-jar", "/app/app.jar"] \ No newline at end of file diff --git a/build.gradle b/build.gradle index ff7b120e1..f62197ea2 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ plugins { // gretty is a gradle plugin to make it easy to run a server and hotswap code at runtime. // https://plugins.gradle.org/plugin/org.gretty - id 'org.gretty' version '3.0.4' + id 'org.gretty' version '3.1.5' // provides access to a database versioning tool. id "org.flywaydb.flyway" version "6.0.8" @@ -63,7 +63,7 @@ plugins { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(11)) + languageVersion.set(JavaLanguageVersion.of(17)) } } diff --git a/desktop_app/gradle/wrapper/gradle-wrapper.properties b/desktop_app/gradle/wrapper/gradle-wrapper.properties index f371643ee..068cdb2dc 100644 --- a/desktop_app/gradle/wrapper/gradle-wrapper.properties +++ b/desktop_app/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/desktop_app/gradle/wrapper/gradle.bat b/desktop_app/gradle/wrapper/gradle.bat new file mode 100644 index 000000000..da9c7040a --- /dev/null +++ b/desktop_app/gradle/wrapper/gradle.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem 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, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME%.. + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\lib\gradle-launcher-7.6.4.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.launcher.GradleMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 000000000..5e69dec1c --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,51 @@ + +services: + jenkins: + build: ./jenkins + privileged: true + user: root + ports: + - 8081:8080 + - 50000:50000 + container_name: jenkins + volumes: + - /home/codespace:/var/jenkins_home + - /var/run/docker.sock:/var/run/docker.sock + networks: + - dev-network + depends_on: + - sonarqube + + sonarqube: + image: sonarqube:8.9-community + container_name: sonarqube + environment: + - SONARQUBE_JDBC_URL=jdbc:postgresql://db:5432/sonar + - SONARQUBE_JDBC_USERNAME=sonar + - SONARQUBE_JDBC_PASSWORD=sonar + ports: + - "9000:9000" + volumes: + - sonarqube_data:/opt/sonarqube/data + networks: + - dev-network + + db: + image: postgres:latest + container_name: sonar-db + environment: + - POSTGRES_USER=sonar + - POSTGRES_PASSWORD=sonar + - POSTGRES_DB=sonar + volumes: + - db_data:/var/lib/postgresql/data + networks: + - dev-network + +networks: + dev-network: + +volumes: + jenkins_home: + sonarqube_data: + db_data: \ No newline at end of file diff --git a/docs/BDD_video.mp4 b/docs/BDD_video.mp4 index 27c4646cf..571239ede 100644 Binary files a/docs/BDD_video.mp4 and b/docs/BDD_video.mp4 differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f371643ee..2e6e5897b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0c8..e69de29bb 100755 --- a/gradlew +++ b/gradlew @@ -1,185 +0,0 @@ -#!/usr/bin/env sh - -# -# Copyright 2015 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. -# - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -exec "$JAVACMD" "$@" diff --git a/jenkins/Dockerfile b/jenkins/Dockerfile new file mode 100644 index 000000000..f112afc1f --- /dev/null +++ b/jenkins/Dockerfile @@ -0,0 +1,29 @@ +FROM jenkins/jenkins:alpine +# switch to root user +USER root + +# install docker on top of the base image +RUN apk add --update docker openrc + +# Install Gradle dependencies +RUN apk add --no-cache \ + openjdk11 \ + bash \ + docker \ + curl \ + unzip + +# Set Gradle version +ENV GRADLE_VERSION=7.6 +ENV GRADLE_HOME=/opt/gradle +# Download and install Gradle +RUN mkdir -p ${GRADLE_HOME} && \ + curl -fsSL https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip -o /tmp/gradle.zip && \ + unzip /tmp/gradle.zip -d /opt/gradle && \ + rm /tmp/gradle.zip + +# Add Gradle to PATH +ENV PATH="${GRADLE_HOME}/gradle-${GRADLE_VERSION}/bin:${PATH}" + +# Verify installation +RUN gradle -v \ No newline at end of file diff --git a/jenkins/Jenkinsfile b/jenkins/Jenkinsfile index d37024fe4..516a828e6 100644 --- a/jenkins/Jenkinsfile +++ b/jenkins/Jenkinsfile @@ -8,6 +8,9 @@ pipeline { // This is set so that the Python API tests will recognize it // and go through the Zap proxy waiting at 9888 HTTP_PROXY = 'http://127.0.0.1:9888' + // Default Java Home for Jenkins (JDK 17) + JAVA_HOME = '/usr/lib/jvm/java-17-openjdk' + PATH = "${JAVA_HOME}/bin:${PATH}" } stages { @@ -15,6 +18,11 @@ pipeline { // build the war file (the binary). This is the only // place that happens. stage('Build') { + environment { + // Override JAVA_HOME to use JDK 11 for this stage + JAVA_HOME = '/usr/lib/jvm/java-11-openjdk' + PATH = "${JAVA_HOME}/bin:${PATH}" + } steps { sh './gradlew clean assemble' } @@ -23,6 +31,11 @@ pipeline { // run all the unit tests - these do not require anything else // to be running and most run very quickly. stage('Unit Tests') { + environment { + // Override JAVA_HOME to use JDK 11 for this stage + JAVA_HOME = '/usr/lib/jvm/java-11-openjdk' + PATH = "${JAVA_HOME}/bin:${PATH}" + } steps { sh './gradlew test' } @@ -36,6 +49,11 @@ pipeline { // run the tests which require connection to a // running database. stage('Database Tests') { + environment { + // Override JAVA_HOME to use JDK 11 for this stage + JAVA_HOME = '/usr/lib/jvm/java-11-openjdk' + PATH = "${JAVA_HOME}/bin:${PATH}" + } steps { sh './gradlew integrate' } @@ -50,6 +68,11 @@ pipeline { // See the files in src/bdd_test // These tests do not require a running system. stage('BDD Tests') { + environment { + // Override JAVA_HOME to use JDK 11 for this stage + JAVA_HOME = '/usr/lib/jvm/java-11-openjdk' + PATH = "${JAVA_HOME}/bin:${PATH}" + } steps { sh './gradlew generateCucumberReports' // generate the code coverage report for jacoco @@ -65,18 +88,18 @@ pipeline { // Runs an analysis of the code, looking for any // patterns that suggest potential bugs. stage('Static Analysis') { - steps { - sh './gradlew sonarqube' - // wait for sonarqube to finish its analysis - sleep 5 - sh './gradlew checkQualityGate' + environment { + // Override JAVA_HOME to use JDK 11 for this stage + JAVA_HOME = '/usr/lib/jvm/java-11-openjdk' + PATH = "${JAVA_HOME}/bin:${PATH}" + } + + steps{ + sh './gradlew sonarqube -Dsonar.host.url=http://sonarqube:9000 -Dsonar.login="admin" -Dsonar.password="ensf400"' } } - - // Move the binary over to the test environment and - // get it running, in preparation for tests that - // require a whole system to be running. + // Might need to remove all code from here to the end stage('Deploy to Test') { steps { sh './gradlew deployToTestWindowsLocal' @@ -87,97 +110,19 @@ pipeline { sh './gradlew waitForHeartBeat' // clear Zap's memory for the incoming tests - sh 'curl http://zap/JSON/core/action/newSession -s --proxy localhost:9888' - } - } - - - // Run the tests which investigate the functioning of the API. - stage('API Tests') { - steps { - sh './gradlew runApiTests' - } - post { - always { - junit 'build/test-results/api_tests/*.xml' - } - } - } - - // We use a BDD framework for some UI tests, Behave, because Python rules - // when it comes to experimentation with UI tests. You can try things and see how they work out. - // this set of BDD tests does require a running system. - // BDD at the UI level is just to ensure that basic capabilities work, - // not that every little detail of UI functionality is correct. For - // that purpose, see the following stage, "UI Tests" - stage('UI BDD Tests') { - steps { - sh './gradlew runBehaveTests' - sh './gradlew generateCucumberReport' - } - post { - always { - junit 'build/test-results/bdd_ui/*.xml' - } + sh 'curl http://zap/JSON/core/action/newSession -s --proxy zap:8080' } } - // This set of tests investigates the functionality of the UI. - // Note that this is separate fom the UI BDD Tests, which - // only focuses on essential capability and therefore only - // covers a small subset of the possibilities of UI behavior. - stage('UI Tests') { - steps { - sh 'cd src/ui_tests/java && ./gradlew clean test' - } - post { - always { - junit 'src/ui_tests/java/build/test-results/test/*.xml' - } - } - } - - // Run OWASP's "DependencyCheck". https://owasp.org/www-project-dependency-check/ - // You are what you eat - and so it is with software. This - // software consists of a number of software by other authors. - // For example, for this project we use language tools by Apache, - // password complexity analysis, and several others. Each one of - // these might have security bugs - and if they have a security - // bug, so do we! - // - // DependencyCheck looks at the list of known - // security vulnerabilities from the United States National Institute of - // Standards and Technology (NIST), and checks if the software - // we are importing has any major known vulnerabilities. If so, - // the build will halt at this point. stage('Security: Dependency Analysis') { steps { sh './gradlew dependencyCheckAnalyze' } } - // Run Jmeter performance testing https://jmeter.apache.org/ - // This test simulates 50 users concurrently using our software - // for a set of common tasks. stage('Performance Tests') { steps { - sh './gradlew runPerfTests' - } - } - - // Runs mutation testing against some subset of our software - // as a spot test. Mutation testing is where bugs are seeded - // into the software and the tests are run, and we see which - // tests fail and which pass, as a result. - // - // what *should* happen is that where code or tests are altered, - // the test should fail, shouldn't it? However, it sometimes - // happens that no matter how code is changed, the tests - // continue to pass, which implies that the test wasn't really - // providing any value for those lines. - stage('Mutation Tests') { - steps { - sh './gradlew pitest' + sh './gradlew runPerfTests --info --stacktrace' } } @@ -190,22 +135,16 @@ pipeline { stage('Collect Zap Security Report') { steps { sh 'mkdir -p build/reports/zap' - sh 'curl http://zap/OTHER/core/other/htmlreport --proxy localhost:9888 > build/reports/zap/zap_report.html' + sh 'curl http://zap/OTHER/core/other/htmlreport --proxy zap:8080 > build/reports/zap/zap_report.html' } } - - // This is the stage where we deploy to production. If any test - // fails, we won't get here. Note that we aren't really doing anything - this - // is a token step, to indicate whether we would have deployed or not. Nothing actually - // happens, since this is a demo project. stage('Deploy to Prod') { steps { - // just a token operation while we pretend to deploy sh 'sleep 5' } } - + } }