diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg new file mode 100644 index 0000000..371a5e7 --- /dev/null +++ b/.github/badges/jacoco.svg @@ -0,0 +1 @@ +coverage96.5% \ No newline at end of file diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..3b10a3c --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,13 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 10 + + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 10 \ No newline at end of file diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..38ac900 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,8 @@ +changelog: + exclude: + authors: + - kitautomation + categories: + - title: Changes + labels: + - "*" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..bc4db44 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,104 @@ +name: CICD + +on: + push: + branches: [ main ] + tags: + - 'v*.*.*' + +jobs: + build: + runs-on: ubuntu-latest + + steps: +# Set docker image names. + - name: Setup env variables + run: | + echo "DOCKER_SERVICE=kvalitetsit/kithugs" >> $GITHUB_ENV + echo "DOCKER_DOCUMENTATION=kvalitetsit/kithugs-documentation" >> $GITHUB_ENV + +# Checkout source code + - uses: actions/checkout@v3 + +# Fail if DOCKER_SERVICE is kithugs and repo is not kithugs. This step can be deleted once + - name: Initial build + run: ./build/failOnFirstBuild.sh + +# Cache maven stuff + - name: Cache local Maven repository + uses: actions/cache@v3 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + +# if below step is skipped this build is a tag build. Can be used for skipping other steps. + - name: Is Tag Build + id: tag + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\/v/} + +# Login to docker hub using secrets in GitHub. + - name: Login to docker + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_PASSWORD }} + + - name: Build and test + run: ./build/build.sh + + - name: Tag service dev docker image + run: ./build/docker-tag.sh ${{ env.DOCKER_SERVICE }}:latest ${{ env.DOCKER_SERVICE }}:dev + + - name: Push service dev docker image + run: ./build/docker-push.sh ${{ env.DOCKER_SERVICE }}:dev + + - name: Tag service git id docker image + run: ./build/docker-tag.sh ${{ env.DOCKER_SERVICE }}:latest ${{ env.DOCKER_SERVICE }}:${{ github.sha }} + + - name: Push service git id docker image. + run: ./build/docker-push.sh ${{ env.DOCKER_SERVICE }}:${{ github.sha }} + + - name: Tag documentation dev docker image + run: ./build/docker-tag.sh ${{ env.DOCKER_DOCUMENTATION }}:latest ${{ env.DOCKER_DOCUMENTATION }}:dev + + - name: Push documentation dev docker image + run: ./build/docker-push.sh ${{ env.DOCKER_DOCUMENTATION }}:dev + + - name: Tag documentation git id docker image + run: ./build/docker-tag.sh ${{ env.DOCKER_DOCUMENTATION }}:latest ${{ env.DOCKER_DOCUMENTATION }}:${{ github.sha }} + + - name: Push documentation git id docker image. + run: ./build/docker-push.sh ${{ env.DOCKER_DOCUMENTATION }}:${{ github.sha }} + + - name: Push latest service docker image + if: ${{ steps.tag.conclusion != 'skipped' }} + run: ./build/docker-push.sh ${{ env.DOCKER_SERVICE }}:latest + + - name: Tag version service docker image + if: ${{ steps.tag.conclusion != 'skipped' }} + run: ./build/docker-tag.sh ${{ env.DOCKER_SERVICE }}:latest ${{ env.DOCKER_SERVICE }}:${{ steps.tag.outputs.VERSION }} + + - name: Push version service docker image. + if: ${{ steps.tag.conclusion != 'skipped' }} + run: ./build/docker-push.sh ${{ env.DOCKER_SERVICE }}:${{ steps.tag.outputs.VERSION }} + + - name: Push latest documentation docker image + if: ${{ steps.tag.conclusion != 'skipped' }} + run: ./build/docker-push.sh ${{ env.DOCKER_DOCUMENTATION }}:latest + + - name: Tag version documentation docker image + if: ${{ steps.tag.conclusion != 'skipped' }} + run: ./build/docker-tag.sh ${{ env.DOCKER_DOCUMENTATION }}:latest ${{ env.DOCKER_DOCUMENTATION }}:${{ steps.tag.outputs.VERSION }} + + - name: Push version documentation docker image. + if: ${{ steps.tag.conclusion != 'skipped' }} + run: ./build/docker-push.sh ${{ env.DOCKER_DOCUMENTATION }}:${{ steps.tag.outputs.VERSION }} + + - name: Create Release Notes + uses: softprops/action-gh-release@v1 + if: ${{ steps.tag.conclusion != 'skipped' }} + with: + generate_release_notes: true diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000..24257f4 --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,28 @@ +name: Dependabot auto-merge +on: pull_request + +permissions: + pull-requests: write + contents: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' && github.repository == 'KvalitetsIT/kithugs' }} + steps: + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v1.6.0 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + - name: Approve PR + run: gh pr review --approve "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + - name: Enable Auto-Merge + if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor' || steps.metadata.outputs.update-type == 'version-update:semver-patch'}} + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/pr_ci.yml b/.github/workflows/pr_ci.yml new file mode 100644 index 0000000..2dd2c1b --- /dev/null +++ b/.github/workflows/pr_ci.yml @@ -0,0 +1,42 @@ +name: Pull Request CI + +on: + pull_request: ~ + +jobs: + pr_build: + runs-on: ubuntu-latest + + steps: +# Checkout source code + - uses: actions/checkout@v3 + +# Cache maven stuff + - name: Cache local Maven repository + uses: actions/cache@v3 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Build and test + run: ./build/build.sh + + - name: Upload Jacoco coverage report + uses: actions/upload-artifact@v3 + with: + name: jacoco-report + path: testreport/target/site/jacoco-aggregate/ + + - name: Save PR number + run: | + mkdir -p ./pr + echo ${{ github.event.number }} > ./pr/NR + + - uses: actions/upload-artifact@v3 + with: + name: pr + path: | + pr/ + testreport/target/site/jacoco-aggregate/jacoco.csv diff --git a/.github/workflows/pr_ci_coverage.yaml b/.github/workflows/pr_ci_coverage.yaml new file mode 100644 index 0000000..99acd3f --- /dev/null +++ b/.github/workflows/pr_ci_coverage.yaml @@ -0,0 +1,63 @@ +name: Update coverage + +on: + workflow_run: + workflows: ["Pull Request CI"] + types: + - completed + +jobs: + coverage: + runs-on: ubuntu-latest + if: > + ${{ github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success' }} + steps: + - name: 'Download artifact' + uses: actions/github-script@v6 + with: + script: | + var artifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{github.event.workflow_run.id }}, + }); + var matchArtifact = artifacts.data.artifacts.filter((artifact) => { + return artifact.name == "pr" + })[0]; + var download = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: 'zip', + }); + var fs = require('fs'); + fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(download.data)); + - run: unzip pr.zip + + - name: Generate Jacoco Badge + id: jacoco + uses: cicirello/jacoco-badge-generator@v2.9.0 + with: + jacoco-csv-file: testreport/target/site/jacoco-aggregate/jacoco.csv + + - name: Log coverage percentage + run: | + echo "coverage = ${{ steps.jacoco.outputs.coverage }}" + + # - name: Update coverage badge + # run: ./build/badge-update.sh + + - name: 'Comment on PR with code coverage' + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + var fs = require('fs'); + var issue_number = Number(fs.readFileSync('./pr/NR')); + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue_number, + body: 'Code coverage after this PR: ' + ${{ steps.jacoco.outputs.coverage }} + }); diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5360cac --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# maven target folder +target + +# IntelliJ stuff +*.iml +.idea + +# Eclipse stuff +.project +.settings/ +.classpath diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6876fd3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 KvalitetsIT + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..20f3ea8 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +![Build Status](https://github.com/KvalitetsIT/kithugs/workflows/CICD/badge.svg) +# kithugs + +Template repository showing how to be a good Java Spring Boot citizen in a k8s cluster. + +## A good citizen + +Below is a set of recommendations for being a good service. The recommendations are not tied to a specific language or +framework. + +1. Configuration through environment variables. +2. Expose readiness endpoint +3. Expose endpoint that Prometheus can scrape +4. Be stateless +5. Support multiple instances +6. Always be in a releasable state +7. Automate build and deployment. + +Some of above recommendations are heavily inspired by [https://12factor.net/](https://12factor.net/). It is recommended +read [https://12factor.net/](https://12factor.net/) for more inspiration and further details. Some points go +further than just being a good service and also touches areas like operations. + +## Getting started + +Run `./setup.sh GIT_REPOSITORY_NAME`. + +Above does a search/replace in relevant files. + +## Endpoints + +### Service + +The service is listening for connections on port 8080. + +Spring boot actuator is listening for connections on port 8081. This is used as prometheus scrape endpoint and health monitoring. + +Prometheus scrape endpoint: `http://localhost:8081/actuator/prometheus` +Health URL that can be used for readiness probe: `http://localhost:8081/actuator/health` + +### Documentation + +Documentation of the API is build as a separate Docker image. Documentation is build using Swagger. The documentation +image is post-fixed with `-documentation`. The file `documentation/docker/compose/docker-compose.yml` contains a setup +that starts both the service and documentation image. The documentation can be accessed at `http://localhost/test` +and the service can be called through the Swagger UI. + +In the docker-compose setup is also an example on how to set custom endpoints for the Swagger documentation service. + +## Dependency updates + +Out of the box we use GitHub Actions as our CI/CD platform and that can also handle dependency updates. We utilize +GitHubs [Dependabot](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuring-dependabot-version-updates) +to create PR's with dependency updates. Further we have a job that automatically approves and merges dependencies. By +default, it is only enabled in the template repository. You can enable this by removing ` && github.repository == 'KvalitetsIT/kithugs'` +from [dependabot-auto-merge.yml](.github/workflows/dependabot-auto-merge.yml). Before enabling it please consider below. + +- If no branch protection rule is configured dependency udpates that fails the automatic build and test will get merged. +- You will not have a chance to review the changes in the dependency updates before it gets merged. +- Enable auto-merge must be enabled in the repository. + +## Configuration + +| Environment variable | Description | Required | +|----------------------|------------------------------------------------------------------------------------------------------|----------| +| JDBC_URL | JDBC connection URL | Yes | +| JDBC_USER | JDBC user | Yes | +| JDBC_PASS | JDBC password | Yes | +| LOG_LEVEL | Log Level for applikation log. Defaults to INFO. | No | +| LOG_LEVEL_FRAMEWORK | Log level for framework. Defaults to INFO. | No | +| CORRELATION_ID | HTTP header to take correlation id from. Used to correlate log messages. Defaults to "x-request-id". | No | diff --git a/build/badge-update.sh b/build/badge-update.sh new file mode 100755 index 0000000..2b1350d --- /dev/null +++ b/build/badge-update.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +if [[ $(git status -s) != '' ]]; then + git config user.name "Github Actions" + git config user.email "development@kvalitetsit.dk" + git add .github/badges/jacoco.svg + git commit -m "Update coverage badge" + git push +else + echo 'Badge not updated. No work to do.' +fi diff --git a/build/build.sh b/build/build.sh new file mode 100755 index 0000000..d5a0c46 --- /dev/null +++ b/build/build.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +# Build resource container and start it +docker build -t resources -f ./integrationtest/docker/Dockerfile-resources --no-cache ./integrationtest/docker +docker run -d --name kithugs-resources resources + +# Build inside docker container +docker run -v /var/run/docker.sock:/var/run/docker.sock -v $HOME/.docker/config.json:/root/.docker/config.json -v $(pwd):/src -v $HOME/.m2:/root/.m2 --volumes-from kithugs-resources maven:3-ibm-semeru-17-focal /src/build/maven.sh diff --git a/build/docker-push.sh b/build/docker-push.sh new file mode 100755 index 0000000..fa8c7fd --- /dev/null +++ b/build/docker-push.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +TAG_NAME=$1 + +if [ -z $TAG_NAME ]; then + echo "TAG_NAME variable not set." + exit 1 +fi + +docker push $TAG_NAME diff --git a/build/docker-tag.sh b/build/docker-tag.sh new file mode 100755 index 0000000..e572b4c --- /dev/null +++ b/build/docker-tag.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +FROM_TAG=$1 +TO_TAG=$2 + +if [ -z "$FROM_TAG" ]; then + echo "FROM_TAG variable not set." + exit 1 +fi + +if [ -z "$TO_TAG" ]; then + echo "TO_TAG variable not set." + exit 1 +fi + +docker tag $FROM_TAG $TO_TAG diff --git a/build/failOnFirstBuild.sh b/build/failOnFirstBuild.sh new file mode 100755 index 0000000..63ac8e4 --- /dev/null +++ b/build/failOnFirstBuild.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +echo "${GITHUB_REPOSITORY}" +echo "${DOCKER_SERVICE}" +if [ "${GITHUB_REPOSITORY}" != "KvalitetsIT/kithugs" ] && [ "${DOCKER_SERVICE}" = "kvalitetsit/kithugs" ]; then + echo "Please run setup.sh REPOSITORY_NAME" + exit 1 +fi diff --git a/build/maven.sh b/build/maven.sh new file mode 100755 index 0000000..da21e1a --- /dev/null +++ b/build/maven.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +apt-get update +apt-get install -y docker.io + +SRC_FOLDER=src + +if [ -d $SRC_FOLDER ]; then + cd $SRC_FOLDER + + mvn clean install -Pdocker-test +else + echo "$SRC_FOLDER folder not found." + exit 1 +fi + diff --git a/documentation/docker/Dockerfile b/documentation/docker/Dockerfile new file mode 100644 index 0000000..f210819 --- /dev/null +++ b/documentation/docker/Dockerfile @@ -0,0 +1,20 @@ +FROM swaggerapi/swagger-ui:v4.12.0 + +COPY /maven/*.yaml /usr/share/nginx/html/ + +COPY /versions /kit/versions +COPY /config/setVersion.sh /kit/setVersion.sh +COPY /config/setServers.sh /kit/setServers.sh +COPY /config/config.sh /config.sh + +COPY /maven/runningVersion.json /kit/runningVersion.json + +RUN apk update && \ + apk add jq && \ + wget -O /usr/bin/yq "https://github.com/mikefarah/yq/releases/download/3.3.2/yq_linux_amd64" && \ + chmod +x /usr/bin/yq && \ + chmod +x /kit/setVersion.sh && \ + chmod +x /kit/setServers.sh && \ + chmod +x /config.sh + +ENTRYPOINT ["sh", "/config.sh"] \ No newline at end of file diff --git a/documentation/docker/compose/docker-compose.yml b/documentation/docker/compose/docker-compose.yml new file mode 100644 index 0000000..9db711d --- /dev/null +++ b/documentation/docker/compose/docker-compose.yml @@ -0,0 +1,41 @@ +version: '2.1' +services: + mariadb: + image: mariadb:10.6 + environment: + - MYSQL_ROOT_PASSWORD=rootroot + - MYSQL_DATABASE=hellodb + - MYSQL_USER=hellouser + - MYSQL_PASSWORD=secret1234 + healthcheck: + test: mysql --user=hellouser --password=secret1234 -e 'show databases;' + interval: 2s + timeout: 2s + retries: 10 + helloservice: + image: kvalitetsit/kithugs:latest + environment: + - jdbc_url=jdbc:mariadb://mariadb:3306/hellodb + - jdbc_user=hellouser + - jdbc_pass=secret1234 + - usercontext_header_name=X-Test-Auth + + - userattributes_role_key=UserRoles + - userattributes_org_key=organisation + + - userrole_admin_values=adminrole + - userrole_user_values=userrole1,userrole2 + - userrole_monitor_values=monitorrole + - userrole_provisioner_values=provisionerrole + depends_on: + mariadb: + condition: service_healthy + ports: + - 8080:8080 + documenatation-and-test: + image: kvalitetsit/kithugs-documentation:latest + environment: + - BASE_URL=/test + - 'SERVER_URLS=[{"url": "http://localhost:8080", "name": "HelloService"}]' + ports: + - 80:8080 diff --git a/documentation/docker/config/config.sh b/documentation/docker/config/config.sh new file mode 100644 index 0000000..9bb63cb --- /dev/null +++ b/documentation/docker/config/config.sh @@ -0,0 +1,14 @@ +#! /bin/sh + +export DOC_FILES=/usr/share/nginx/html/*.yaml + +echo "Running set version" +/kit/setVersion.sh +echo "Running set servers" +/kit/setServers.sh + +echo "Sets env URLS to list of versions" +export URLS=$(cat /kit/env) + +echo "Restarting nginx" +/docker-entrypoint.sh nginx -g 'daemon off;' \ No newline at end of file diff --git a/documentation/docker/config/setServers.sh b/documentation/docker/config/setServers.sh new file mode 100644 index 0000000..5235f60 --- /dev/null +++ b/documentation/docker/config/setServers.sh @@ -0,0 +1,20 @@ +#! /bin/sh + +if [[ -z "${SERVER_URLS}" ]]; then + echo "SERVER_URLS NOT set" +else + for file in $DOC_FILES + do + yq d -i $file 'servers[*]' + + for i in $(echo $SERVER_URLS | jq -r '. | keys | .[]'); do + + yq w -i $file 'servers[+].url' $(echo $SERVER_URLS | jq -r ".[$i].url") + yq w -i $file "servers[$i].description" $(echo $SERVER_URLS | jq -r ".[$i].name") + + done + + done +fi + + diff --git a/documentation/docker/config/setVersion.sh b/documentation/docker/config/setVersion.sh new file mode 100644 index 0000000..1c010eb --- /dev/null +++ b/documentation/docker/config/setVersion.sh @@ -0,0 +1,47 @@ +#!/bin/sh + +function updateFile { + local f="$1" + cmp -s "$f" "$f.tmp" + if [ $? = 0 ] ; then + /bin/rm $f.tmp + else + /bin/mv "$f.tmp" "$f" + fi +} + +echo "Add Dev version to list of versions" +GIT_BRANCH=$(cat /kit/runningVersion.json | jq -r '."git.commit.id.describe"') +echo "[]" > /kit/env + +if (echo "$GIT_BRANCH" | grep -Eq ^v[0-9]*\\.[0-9]*\\.[0-9]*$); then + echo "Release version" +else + echo "Is dev version" + + url="${BASE_URL}/${GIT_BRANCH}.yaml" + + cat /kit/env | jq --arg u $url '. += [{"name": "Dev", "url": $u }] | sort_by(.name)' > /kit/env.tmp + updateFile /kit/env +fi + +echo "Creating file with version and path" +( + IFS=$'\n' + for version in $(cat kit/versions) + do + url="${BASE_URL}/${version}.yaml" + + cat /kit/env | jq --arg n $version --arg u $url '. += [{"name": $n, "url": $u}]' > /kit/env.tmp + updateFile /kit/env + done +) + + + +echo "Updates version in doc file" +for file in $DOC_FILES +do + file_name=${file##*/} + yq w -i $file 'info.version' $(echo ${file_name%.*} | cut -d "v" -f 2) +done \ No newline at end of file diff --git a/documentation/docker/getOldDoc.sh b/documentation/docker/getOldDoc.sh new file mode 100755 index 0000000..af059db --- /dev/null +++ b/documentation/docker/getOldDoc.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +if docker pull kvalitetsit/kithugs-documentation:latest; then + echo "Copy from old documentation image." + docker cp $(docker create kvalitetsit/kithugs-documentation:latest):/usr/share/nginx/html target/old +fi diff --git a/documentation/docker/versions b/documentation/docker/versions new file mode 100644 index 0000000..f979ade --- /dev/null +++ b/documentation/docker/versions @@ -0,0 +1 @@ +v0.9.0 diff --git a/documentation/pom.xml b/documentation/pom.xml new file mode 100644 index 0000000..b8bc6c3 --- /dev/null +++ b/documentation/pom.xml @@ -0,0 +1,239 @@ + + 4.0.0 + + kithugs + dk.kvalitetsit.kithugs + 0.0.1-SNAPSHOT + + documentation + + + + + org.openapitools + jackson-databind-nullable + + + + org.springframework.boot + spring-boot-starter-validation + + + + io.swagger.core.v3 + swagger-annotations + + + + io.swagger + swagger-annotations + + + + javax.ws.rs + javax.ws.rs-api + + + + org.springframework + spring-web + + + + + com.google.code.findbugs + jsr305 + test + + + + com.squareup.okhttp3 + okhttp + test + + + + com.squareup.okhttp3 + logging-interceptor + 4.11.0 + test + + + + com.google.code.gson + gson + test + + + + io.gsonfire + gson-fire + test + + + + javax.annotation + javax.annotation-api + test + + + + + + exec-maven-plugin + org.codehaus.mojo + 3.1.0 + + + Get old doc versions + compile + + exec + + + docker/getOldDoc.sh + + + + + + + io.fabric8 + docker-maven-plugin + + + + kvalitetsit/kithugs-documentation + + + dev + latest + ${git.commit.id} + + ${project.basedir}/docker + + + + + ${project.basedir}/src/main/resources/helloservice.yaml + ${git.commit.id.describe}.yaml + + + ${project.build.outputDirectory}/git.properties + runningVersion.json + + + + + target/old + / + + + + artifact + /app + + + + + true + + + + build-image + package + + build + + + + + + org.openapitools + openapi-generator-maven-plugin + 6.6.0 + + + + generate + + + ${project.basedir}/src/main/resources/helloservice.yaml + java + + src/test/java + java8 + + ${project.build.directory}/generated-test-sources/openapi/test + false + false + true + false + + + + server + + generate + + + ${project.basedir}/src/main/resources/helloservice.yaml + spring + + src/main/java + java8 + true + true + spring-cloud + true + + ${project.build.directory}/generated-sources/openapi/main + false + false + false + + + + + + pl.project13.maven + git-commit-id-plugin + + + get-the-git-infos + + revision + + initialize + + + + true + ${project.build.outputDirectory}/git.properties + + git.commit.id.describe + + full + + true + + json + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + test-jar + + + + + + + \ No newline at end of file diff --git a/documentation/src/main/resources/helloservice.yaml b/documentation/src/main/resources/helloservice.yaml new file mode 100644 index 0000000..c287f95 --- /dev/null +++ b/documentation/src/main/resources/helloservice.yaml @@ -0,0 +1,125 @@ +openapi: 3.0.0 +info: + title: Kithugs + description: API description for KITHUGS. + version: "1.0.0" + contact: + email: development@kvalitetitsit.dk +tags: + - name: KITHUGS + description: KITHUGS related API's + +servers: + - url: '{protocol}://{environment}:{port}' + variables: + protocol: + enum: + - http + - https + default: http + environment: + enum: + - localhost # Docker-compose setup + default: localhost # Development + port: + enum: + - 8080 + default: 8080 +paths: + /v1/hello: + get: + tags: + - KITHUGS + parameters: + - name: name + in: query + required: true + description: Name. If name is "NOT_VALID" an validation error occurs. + schema: + type: string + maxLength: 10 + example: 'John Doe' + summary: Call the thugs service + description: Post your name and get some hugs or meet some thugs. + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/helloResponse' + '400': + $ref: '#/components/responses/400' + + +components: + responses: + '400': + description: "Bad Request. This could be because: * One of the required parameters/properties are missing or is empty
* Length of input is exceeding maximum length
(See a possible detailed error message in the in the response.)" + content: + application/json: + schema: + $ref: '#/components/schemas/detailedError' + '401': + description: "Unauthorized. This could be because:
* The calling system has not been properly authenticated." + '403': + description: "Forbidden. This could be because:
* The requested information does not belong the organisation of the user
* The calling user does not have the required roles" + + schemas: + helloResponse: + type: object + required: + - name + - now + properties: + name: + description: Your name + type: string + example: "John Doe" + now: + description: Time request was received + type: string + format: date-time + i_can_be_null: + type: string + nullable: true + + detailedError: + allOf: + - $ref: '#/components/schemas/basicError' + - type: object + required: + - detailed_error + - detailed_error_code + properties: + detailed_error: + description: Detailed error text. This could be a text describing an validation error. + type: string + detailed_error_code: + description: + type: string + enum: + - 10 + - 20 + + basicError: + type: object + required: + - timestamp + - status + - error + - path + properties: + error: + description: Error message. + type: string + path: + description: Path + type: string + status: + description: HTTP status code + type: integer + timestamp: + description: Time of error + type: string + format: date-time diff --git a/integrationtest/docker/Dockerfile b/integrationtest/docker/Dockerfile new file mode 100644 index 0000000..e32ccbe --- /dev/null +++ b/integrationtest/docker/Dockerfile @@ -0,0 +1,12 @@ +FROM kvalitetsit/kithugs:dev + +COPY /maven/integrationtest.jar /app/lib/integrationtest.jar +COPY /maven/org.jacoco.agent-*.jar /jacoco/jacoco-agent.jar + +USER root + +RUN apt-get update && \ + apt-get install -y unzip && \ + unzip /jacoco/jacoco-agent.jar jacocoagent.jar -d /jacoco/ + +USER appuser \ No newline at end of file diff --git a/integrationtest/docker/Dockerfile-resources b/integrationtest/docker/Dockerfile-resources new file mode 100644 index 0000000..88f3ea4 --- /dev/null +++ b/integrationtest/docker/Dockerfile-resources @@ -0,0 +1,9 @@ +FROM bash:latest + +# Jacoco agent jar file. +VOLUME /jacoco-report + +# Loop script to avoid termination of container. +ADD ./loop.sh /loop.sh + +CMD [ "/loop.sh" ] diff --git a/integrationtest/docker/loop.sh b/integrationtest/docker/loop.sh new file mode 100755 index 0000000..0fc183b --- /dev/null +++ b/integrationtest/docker/loop.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +chmod 777 /jacoco-report + +while true; do + sleep 1 +done + diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml new file mode 100644 index 0000000..f645cb2 --- /dev/null +++ b/integrationtest/pom.xml @@ -0,0 +1,257 @@ + + + + kithugs + dk.kvalitetsit.kithugs + 0.0.1-SNAPSHOT + + 4.0.0 + + integrationtest + + + + org.testcontainers + mockserver + test + + + + org.testcontainers + mariadb + test + + + + org.mock-server + mockserver-client-java + test + + + + + org.jacoco + org.jacoco.agent + + + + org.mariadb.jdbc + mariadb-java-client + test + + + + org.glassfish.jersey.core + jersey-client + test + + + + org.glassfish.jersey.core + jersey-common + test + + + + org.glassfish.jersey.inject + jersey-hk2 + test + + + + org.glassfish.jersey.media + jersey-media-json-jackson + test + + + + ch.qos.logback + logback-classic + test + + + + dk.kvalitetsit.kithugs + web + test + + + + dk.kvalitetsit.kithugs + documentation + ${project.version} + test-jar + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.squareup.okhttp3 + logging-interceptor + 4.11.0 + test + + + + com.google.code.findbugs + jsr305 + test + + + + com.squareup.okhttp3 + okhttp + test + + + + com.google.code.gson + gson + test + + + + io.gsonfire + gson-fire + test + + + + + + + io.fabric8 + docker-maven-plugin + + + + local/kithugs-qa + + + dev + latest + ${git.commit.id} + + ${basedir}/docker + + artifact-with-dependencies + /app + + + + + true + + + + build-image + package + + build + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + org.jacoco + jacoco-maven-plugin + + + exec-merge + verify + + merge + + + + + /jacoco-report + + **/*.exec + + + + /tmp + + **/*.exec + + + + + + + + + + + + + build + + true + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + false + + + + + + integration-test + verify + + + + + + + + + + docker-test + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + true + + + + + + integration-test + verify + + + + + + + + + + \ No newline at end of file diff --git a/integrationtest/src/main/resources/db/migration/V901__extra_data_for_integration_test.sql b/integrationtest/src/main/resources/db/migration/V901__extra_data_for_integration_test.sql new file mode 100644 index 0000000..d932408 --- /dev/null +++ b/integrationtest/src/main/resources/db/migration/V901__extra_data_for_integration_test.sql @@ -0,0 +1 @@ +insert into hello_table(name) values('Some Name'); diff --git a/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/AbstractIntegrationTest.java b/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/AbstractIntegrationTest.java new file mode 100644 index 0000000..cfb54cb --- /dev/null +++ b/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/AbstractIntegrationTest.java @@ -0,0 +1,54 @@ +package dk.kvalitetsit.hello.integrationtest; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; + +import java.io.IOException; +import java.net.URISyntaxException; + +public abstract class AbstractIntegrationTest { + private static final Logger logger = LoggerFactory.getLogger(AbstractIntegrationTest.class); + + private static GenericContainer helloService; + private static String apiBasePath; + + static { + Runtime.getRuntime().addShutdownHook(new Thread() + { + public void run() + { + if(helloService != null) { + logger.info("Stopping hello service container: " + helloService.getContainerId()); + helloService.getDockerClient().stopContainerCmd(helloService.getContainerId()).exec(); + } + } + }); + + try { + setup(); + } catch (IOException | URISyntaxException e) { + throw new RuntimeException(e); + } + } + + private static void setup() throws IOException, URISyntaxException { + var runInDocker = Boolean.getBoolean("runInDocker"); + logger.info("Running integration test in docker container: " + runInDocker); + + ServiceStarter serviceStarter; + serviceStarter = new ServiceStarter(); + if(runInDocker) { + helloService = serviceStarter.startServicesInDocker(); + apiBasePath = "http://" + helloService.getContainerIpAddress() + ":" + helloService.getMappedPort(8080); + } + else { + serviceStarter.startServices(); + apiBasePath = "http://localhost:8080"; + } + } + + String getApiBasePath() { + return apiBasePath; + } +} diff --git a/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/HelloIT.java b/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/HelloIT.java new file mode 100644 index 0000000..8e58fb3 --- /dev/null +++ b/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/HelloIT.java @@ -0,0 +1,63 @@ +package dk.kvalitetsit.hello.integrationtest; + +import org.junit.Assert; +import org.junit.Test; +import org.openapitools.client.ApiClient; +import org.openapitools.client.ApiException; +import org.openapitools.client.JSON; +import org.openapitools.client.api.KithugsApi; +import org.openapitools.client.model.DetailedError; + +import java.util.UUID; + +import static org.junit.Assert.*; + +public class HelloIT extends AbstractIntegrationTest { + + private final KithugsApi helloApi; + private final JSON json; + + public HelloIT() { + var apiClient = new ApiClient(); + apiClient.setBasePath(getApiBasePath()); + + json = apiClient.getJSON(); + helloApi = new KithugsApi(apiClient); + } + + @Test + public void testCallService() throws ApiException { + var input = "John Dow"; + + var result = helloApi.v1HelloGet(input); + + assertNotNull(result); + assertEquals(input, result.getName()); + assertNull(result.getiCanBeNull()); + assertNotNull(result.getNow()); + } + + @Test + public void testCallServiceNameTooLong() throws ApiException { + var input = "John Doe Is Too Long"; + + var thrownException = Assert.assertThrows(ApiException.class, () -> helloApi.v1HelloGet(input)); + assertEquals(500, thrownException.getCode()); + } + + @Test + public void testCallServiceNameValidationError() throws ApiException { + var input = "NOT_VALID"; + + var thrownException = Assert.assertThrows(ApiException.class, () -> helloApi.v1HelloGet(input)); + assertEquals(400, thrownException.getCode()); + + DetailedError detailedError = JSON.deserialize(thrownException.getResponseBody(), DetailedError.class); + assertEquals("Bad Request", detailedError.getError()); + assertEquals("/v1/hello", detailedError.getPath()); + assertEquals("NOT_VALID is not a valid name.", detailedError.getDetailedError()); + assertEquals(DetailedError.DetailedErrorCodeEnum._10, detailedError.getDetailedErrorCode()); + assertNotNull(detailedError.getTimestamp()); + assertEquals(400, detailedError.getStatus().longValue()); + } +} diff --git a/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/ServiceStarter.java b/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/ServiceStarter.java new file mode 100644 index 0000000..b5c0024 --- /dev/null +++ b/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/ServiceStarter.java @@ -0,0 +1,110 @@ +package dk.kvalitetsit.hello.integrationtest; + +import com.github.dockerjava.api.model.VolumesFrom; +import dk.kvalitetsit.hello.Application; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.testcontainers.DockerClientFactory; +import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.Wait; + +import java.util.Collections; + +public class ServiceStarter { + private static final Logger logger = LoggerFactory.getLogger(ServiceStarter.class); + private static final Logger serviceLogger = LoggerFactory.getLogger("kithugs"); + private static final Logger mariadbLogger = LoggerFactory.getLogger("mariadb"); + + private Network dockerNetwork; + private String jdbcUrl; + + public void startServices() { + dockerNetwork = Network.newNetwork(); + + setupDatabaseContainer(); + + System.setProperty("JDBC.URL", jdbcUrl); + System.setProperty("JDBC.USER", "hellouser"); + System.setProperty("JDBC.PASS", "secret1234"); + + SpringApplication.run(Application.class); + } + + public GenericContainer startServicesInDocker() { + dockerNetwork = Network.newNetwork(); + + setupDatabaseContainer(); + + var resourcesContainerName = "kithugs-resources"; + var resourcesRunning = containerRunning(resourcesContainerName); + logger.info("Resource container is running: " + resourcesRunning); + + GenericContainer service; + + // Start service + if (resourcesRunning) { + VolumesFrom volumesFrom = new VolumesFrom(resourcesContainerName); + service = new GenericContainer<>("local/kithugs-qa:dev") + .withCreateContainerCmdModifier(modifier -> modifier.withVolumesFrom(volumesFrom)) + .withEnv("JVM_OPTS", "-javaagent:/jacoco/jacocoagent.jar=output=file,destfile=/jacoco-report/jacoco-it.exec,dumponexit=true,append=true -cp integrationtest.jar"); + } else { + service = new GenericContainer<>("local/kithugs-qa:dev") + .withFileSystemBind("/tmp", "/jacoco-report/") + .withEnv("JVM_OPTS", "-javaagent:/jacoco/jacocoagent.jar=output=file,destfile=/jacoco-report/jacoco-it.exec,dumponexit=true -cp integrationtest.jar"); + } + + service.withNetwork(dockerNetwork) + .withNetworkAliases("kithugs") + + .withEnv("LOG_LEVEL", "INFO") + + .withEnv("JDBC_URL", "jdbc:mariadb://mariadb:3306/hellodb") + .withEnv("JDBC_USER", "hellouser") + .withEnv("JDBC_PASS", "secret1234") + + .withEnv("spring.flyway.locations", "classpath:db/migration,filesystem:/app/sql") + .withClasspathResourceMapping("db/migration/V901__extra_data_for_integration_test.sql", "/app/sql/V901__extra_data_for_integration_test.sql", BindMode.READ_ONLY) +// .withEnv("JVM_OPTS", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000") + + .withExposedPorts(8081,8080) + .waitingFor(Wait.forHttp("/actuator").forPort(8081).forStatusCode(200)); + service.start(); + attachLogger(serviceLogger, service); + + return service; + } + + private boolean containerRunning(String containerName) { + return DockerClientFactory + .instance() + .client() + .listContainersCmd() + .withNameFilter(Collections.singleton(containerName)) + .exec() + .size() != 0; + } + + private void setupDatabaseContainer() { + // Database server for Organisation. + MariaDBContainer mariadb = new MariaDBContainer<>("mariadb:10.6") + .withDatabaseName("hellodb") + .withUsername("hellouser") + .withPassword("secret1234") + .withNetwork(dockerNetwork) + .withNetworkAliases("mariadb"); + mariadb.start(); + jdbcUrl = mariadb.getJdbcUrl(); + attachLogger(mariadbLogger, mariadb); + } + + private void attachLogger(Logger logger, GenericContainer container) { + ServiceStarter.logger.info("Attaching logger to container: " + container.getContainerInfo().getName()); + Slf4jLogConsumer logConsumer = new Slf4jLogConsumer(logger); + container.followOutput(logConsumer); + } +} diff --git a/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/TestApplication.java b/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/TestApplication.java new file mode 100644 index 0000000..70bfda0 --- /dev/null +++ b/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/TestApplication.java @@ -0,0 +1,11 @@ +package dk.kvalitetsit.hello.integrationtest; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +@SpringBootApplication +public class TestApplication extends SpringBootServletInitializer { + public static void main(String[] args) { + new ServiceStarter().startServices(); + } +} diff --git a/integrationtest/src/test/resources/logback-test.xml b/integrationtest/src/test/resources/logback-test.xml new file mode 100644 index 0000000..54b6517 --- /dev/null +++ b/integrationtest/src/test/resources/logback-test.xml @@ -0,0 +1,17 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - correlation-id: %X{correlation-id} - %msg%n + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..d694a58 --- /dev/null +++ b/pom.xml @@ -0,0 +1,248 @@ + + + 4.0.0 + pom + + + web + service + integrationtest + testreport + documentation + + + org.springframework.boot + spring-boot-starter-parent + 3.1.2 + + dk.kvalitetsit.kithugs + kithugs + 0.0.1-SNAPSHOT + kithugs + KITHUGS service + + + 17 + 1.18.3 + 2.0.2 + 2.0.0 + 0.8.10 + + + + + org.junit.vintage + junit-vintage-engine + test + + + + + + + + dk.kvalitetsit.kithugs + service + ${project.version} + + + + dk.kvalitetsit.kithugs + integrationtest + ${project.version} + + + + dk.kvalitetsit.kithugs + web + ${project.version} + + + + dk.kvalitetsit.kithugs + documentation + ${project.version} + + + + + net.logstash.logback + logstash-logback-encoder + 7.4 + + + + + dk.kvalitetsit + spring-prometheus-app-info + ${spring-prometheus-app-info-version} + + + + + dk.kvalitetsit + spring-request-id-logger + ${spring-request-id-logger-version} + + + + + org.openapitools + jackson-databind-nullable + 0.2.6 + + + + + io.swagger.core.v3 + swagger-annotations + 2.2.15 + + + + io.swagger + swagger-annotations + 1.6.11 + + + + + javax.ws.rs + javax.ws.rs-api + 2.1.1 + + + + + + org.testcontainers + mockserver + ${testcontainers.version} + + + + org.testcontainers + mariadb + ${testcontainers.version} + + + + org.mock-server + mockserver + 3.12 + + + + org.mock-server + mockserver-client-java + 5.15.0 + + + + + org.jacoco + org.jacoco.agent + ${jacoco-version} + + + + + com.google.code.findbugs + jsr305 + 3.0.2 + test + + + + com.squareup.okhttp3 + okhttp + 4.11.0 + test + + + + io.gsonfire + gson-fire + 1.8.5 + test + + + + javax.annotation + javax.annotation-api + 1.3.2 + test + + + + + + + + ${project.artifactId} + + + + io.fabric8 + docker-maven-plugin + 0.43.2 + + + + + + + pl.project13.maven + git-commit-id-plugin + 4.9.10 + + + + revision + + + + + git + dd.MM.yyyy '@' HH:mm:ss z + true + false + ${project.basedir}/.git + false + true + target/classes/git.properties + false + 7 + true + false + + false + false + -dirty + false + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + + + org.jacoco + jacoco-maven-plugin + ${jacoco-version} + + + + prepare-agent + + + + + + + diff --git a/service/pom.xml b/service/pom.xml new file mode 100644 index 0000000..759628d --- /dev/null +++ b/service/pom.xml @@ -0,0 +1,108 @@ + + + + kithugs + dk.kvalitetsit.kithugs + 0.0.1-SNAPSHOT + + 4.0.0 + + service + + + + dk.kvalitetsit.kithugs + documentation + + + + + org.springframework + spring-context + + + + org.springframework + spring-web + + + + org.springframework + spring-beans + + + + + org.springframework.boot + spring-boot-starter-data-jdbc + + + + org.mariadb.jdbc + mariadb-java-client + + + + org.flywaydb + flyway-mysql + + + + + org.apache.commons + commons-lang3 + + + + + dk.kvalitetsit + spring-prometheus-app-info + + + + + dk.kvalitetsit + spring-request-id-logger + + + + + jakarta.servlet + jakarta.servlet-api + provided + + + + + ch.qos.logback + logback-classic + + + + + junit + junit + test + + + + org.mockito + mockito-core + test + + + + org.testcontainers + mariadb + test + + + + org.springframework.boot + spring-boot-starter-test + test + + + \ No newline at end of file diff --git a/service/src/main/java/dk/kvalitetsit/hello/configuration/DatabaseConfiguration.java b/service/src/main/java/dk/kvalitetsit/hello/configuration/DatabaseConfiguration.java new file mode 100644 index 0000000..b493300 --- /dev/null +++ b/service/src/main/java/dk/kvalitetsit/hello/configuration/DatabaseConfiguration.java @@ -0,0 +1,30 @@ +package dk.kvalitetsit.hello.configuration; + +import dk.kvalitetsit.hello.dao.HelloDao; +import dk.kvalitetsit.hello.dao.HelloDaoImpl; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.sql.DataSource; + +@Configuration +@EnableTransactionManagement +public class DatabaseConfiguration { + @Bean + public HelloDao helloDao(DataSource dataSource) { + return new HelloDaoImpl(dataSource); + } + + @Bean + public DataSource dataSource(@Value("${jdbc.url}") String jdbcUrl, @Value("${jdbc.user}") String jdbcUser, @Value("${jdbc.pass}") String jdbcPass) { + DriverManagerDataSource dataSource = new DriverManagerDataSource(); + dataSource.setUrl(jdbcUrl); + dataSource.setUsername(jdbcUser); + dataSource.setPassword(jdbcPass); + + return dataSource; + } +} \ No newline at end of file diff --git a/service/src/main/java/dk/kvalitetsit/hello/configuration/HelloConfiguration.java b/service/src/main/java/dk/kvalitetsit/hello/configuration/HelloConfiguration.java new file mode 100644 index 0000000..cf68c5e --- /dev/null +++ b/service/src/main/java/dk/kvalitetsit/hello/configuration/HelloConfiguration.java @@ -0,0 +1,14 @@ +package dk.kvalitetsit.hello.configuration; + +import dk.kvalitetsit.hello.service.HelloService; +import dk.kvalitetsit.hello.service.HelloServiceImpl; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class HelloConfiguration{ + @Bean + public HelloService helloService() { + return new HelloServiceImpl(); + } +} diff --git a/service/src/main/java/dk/kvalitetsit/hello/configuration/JacksonConfiguration.java b/service/src/main/java/dk/kvalitetsit/hello/configuration/JacksonConfiguration.java new file mode 100644 index 0000000..457913e --- /dev/null +++ b/service/src/main/java/dk/kvalitetsit/hello/configuration/JacksonConfiguration.java @@ -0,0 +1,14 @@ +package dk.kvalitetsit.hello.configuration; + +import com.fasterxml.jackson.databind.Module; +import org.openapitools.jackson.nullable.JsonNullableModule; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class JacksonConfiguration { + @Bean + public Module jsonNullableModule() { + return new JsonNullableModule(); + } +} diff --git a/service/src/main/java/dk/kvalitetsit/hello/controller/ErrorController.java b/service/src/main/java/dk/kvalitetsit/hello/controller/ErrorController.java new file mode 100644 index 0000000..6366a14 --- /dev/null +++ b/service/src/main/java/dk/kvalitetsit/hello/controller/ErrorController.java @@ -0,0 +1,31 @@ +package dk.kvalitetsit.hello.controller; + +import dk.kvalitetsit.hello.controller.exception.AbstractApiException; +import jakarta.servlet.http.HttpServletRequest; +import org.openapitools.model.DetailedError; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +import java.time.OffsetDateTime; + +@ControllerAdvice +public class ErrorController { + private static final Logger logger = LoggerFactory.getLogger(ErrorController.class); + + @ExceptionHandler(AbstractApiException.class) + public ResponseEntity handleApiException(AbstractApiException e, HttpServletRequest request) { + logger.debug("Handling ApiException: {}", e.getHttpStatus()); + var error = new DetailedError() + .path(request.getRequestURI()) + .timestamp(OffsetDateTime.now()) + .status(e.getHttpStatus().value()) + .error(e.getHttpStatus().getReasonPhrase()) + .detailedErrorCode(e.getDetailedErrorCode()) + .detailedError(e.getDetailedError()); + + return ResponseEntity.status(e.getHttpStatus().value()).body(error); + } +} diff --git a/service/src/main/java/dk/kvalitetsit/hello/controller/HelloController.java b/service/src/main/java/dk/kvalitetsit/hello/controller/HelloController.java new file mode 100644 index 0000000..69d44c8 --- /dev/null +++ b/service/src/main/java/dk/kvalitetsit/hello/controller/HelloController.java @@ -0,0 +1,44 @@ +package dk.kvalitetsit.hello.controller; + +import dk.kvalitetsit.hello.controller.exception.BadRequestException; +import dk.kvalitetsit.hello.service.HelloService; +import dk.kvalitetsit.hello.service.model.HelloServiceInput; +import org.openapitools.api.KithugsApi; +import org.openapitools.model.DetailedError; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.RestController; + +@RestController +// CORS - Consider if this is needed in your application. Only here to make Swagger UI work. +@CrossOrigin(origins = "http://localhost") +public class HelloController implements KithugsApi { + private static final Logger logger = LoggerFactory.getLogger(HelloController.class); + private final HelloService helloService; + + public HelloController(HelloService helloService) { + this.helloService = helloService; + } + + @Override + public ResponseEntity v1HelloGet(String name) { + logger.debug("Enter GET hello."); + + // Just for demonstrating error response. Actual validation should most likely be somewhere else. + if(name.equalsIgnoreCase("NOT_VALID")) { + throw new BadRequestException(DetailedError.DetailedErrorCodeEnum._10, "%s is not a valid name.".formatted(name)); + } + + var serviceInput = new HelloServiceInput(name); + + var serviceResponse = helloService.helloServiceBusinessLogic(serviceInput); + + var helloResponse = new org.openapitools.model.HelloResponse(); + helloResponse.setName(serviceResponse.name()); + helloResponse.setNow(serviceResponse.now().toOffsetDateTime()); + + return ResponseEntity.ok(helloResponse); + } +} diff --git a/service/src/main/java/dk/kvalitetsit/hello/controller/exception/AbstractApiException.java b/service/src/main/java/dk/kvalitetsit/hello/controller/exception/AbstractApiException.java new file mode 100644 index 0000000..998c586 --- /dev/null +++ b/service/src/main/java/dk/kvalitetsit/hello/controller/exception/AbstractApiException.java @@ -0,0 +1,28 @@ +package dk.kvalitetsit.hello.controller.exception; + +import org.openapitools.model.DetailedError; +import org.springframework.http.HttpStatus; + +public class AbstractApiException extends RuntimeException { + private final HttpStatus httpStatus; + private final DetailedError.DetailedErrorCodeEnum detailedErrorCodeEnum; + private final String detailedError; + + public AbstractApiException(HttpStatus httpStatus, DetailedError.DetailedErrorCodeEnum detailedErrorCodeEnum, String detailedErrorCode) { + this.httpStatus = httpStatus; + this.detailedErrorCodeEnum = detailedErrorCodeEnum; + this.detailedError = detailedErrorCode; + } + + public HttpStatus getHttpStatus() { + return httpStatus; + } + + public DetailedError.DetailedErrorCodeEnum getDetailedErrorCode() { + return detailedErrorCodeEnum; + } + + public String getDetailedError() { + return detailedError; + } +} diff --git a/service/src/main/java/dk/kvalitetsit/hello/controller/exception/BadRequestException.java b/service/src/main/java/dk/kvalitetsit/hello/controller/exception/BadRequestException.java new file mode 100644 index 0000000..9db7b75 --- /dev/null +++ b/service/src/main/java/dk/kvalitetsit/hello/controller/exception/BadRequestException.java @@ -0,0 +1,11 @@ +package dk.kvalitetsit.hello.controller.exception; + +import org.openapitools.model.DetailedError; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +public class BadRequestException extends AbstractApiException { + public BadRequestException(DetailedError.DetailedErrorCodeEnum errorCode, String errorText) { + super(HttpStatus.BAD_REQUEST, errorCode, errorText); + } +} diff --git a/service/src/main/java/dk/kvalitetsit/hello/dao/HelloDao.java b/service/src/main/java/dk/kvalitetsit/hello/dao/HelloDao.java new file mode 100644 index 0000000..bf3b54a --- /dev/null +++ b/service/src/main/java/dk/kvalitetsit/hello/dao/HelloDao.java @@ -0,0 +1,11 @@ +package dk.kvalitetsit.hello.dao; + +import dk.kvalitetsit.hello.dao.entity.HelloEntity; + +import java.util.List; + +public interface HelloDao { + void insert(HelloEntity helloEntity); + + List findAll(); +} \ No newline at end of file diff --git a/service/src/main/java/dk/kvalitetsit/hello/dao/HelloDaoImpl.java b/service/src/main/java/dk/kvalitetsit/hello/dao/HelloDaoImpl.java new file mode 100644 index 0000000..29c53e5 --- /dev/null +++ b/service/src/main/java/dk/kvalitetsit/hello/dao/HelloDaoImpl.java @@ -0,0 +1,39 @@ +package dk.kvalitetsit.hello.dao; + +import dk.kvalitetsit.hello.dao.entity.HelloEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.jdbc.core.DataClassRowMapper; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; + +import javax.sql.DataSource; +import java.util.HashMap; +import java.util.List; + +public class HelloDaoImpl implements HelloDao { + private static final Logger logger = LoggerFactory.getLogger(HelloDaoImpl.class); + private final NamedParameterJdbcTemplate template; + + public HelloDaoImpl(DataSource dataSource) { + template = new NamedParameterJdbcTemplate(dataSource); + } + + @Override + public void insert(HelloEntity helloEntity) { + logger.info("Inserting new entry in database."); + + var sql = "insert into hello_table(name) values(:name)"; + + var parameterMap = new HashMap(); + parameterMap.put("name", helloEntity.name()); + + template.update(sql, parameterMap); + } + + @Override + public List findAll() { + var sql = "select * from hello_table"; + + return template.query(sql, new DataClassRowMapper<>(HelloEntity.class)); + } +} diff --git a/service/src/main/java/dk/kvalitetsit/hello/dao/entity/HelloEntity.java b/service/src/main/java/dk/kvalitetsit/hello/dao/entity/HelloEntity.java new file mode 100644 index 0000000..4f5576d --- /dev/null +++ b/service/src/main/java/dk/kvalitetsit/hello/dao/entity/HelloEntity.java @@ -0,0 +1,7 @@ +package dk.kvalitetsit.hello.dao.entity; + +public record HelloEntity(Long id, String name) { + public static HelloEntity createInstance(String name) { + return new HelloEntity(null, name); + } +} diff --git a/service/src/main/java/dk/kvalitetsit/hello/service/HelloService.java b/service/src/main/java/dk/kvalitetsit/hello/service/HelloService.java new file mode 100644 index 0000000..e2771ee --- /dev/null +++ b/service/src/main/java/dk/kvalitetsit/hello/service/HelloService.java @@ -0,0 +1,8 @@ +package dk.kvalitetsit.hello.service; + +import dk.kvalitetsit.hello.service.model.HelloServiceOutput; +import dk.kvalitetsit.hello.service.model.HelloServiceInput; + +public interface HelloService { + HelloServiceOutput helloServiceBusinessLogic(HelloServiceInput input); +} diff --git a/service/src/main/java/dk/kvalitetsit/hello/service/HelloServiceImpl.java b/service/src/main/java/dk/kvalitetsit/hello/service/HelloServiceImpl.java new file mode 100644 index 0000000..8fbfdee --- /dev/null +++ b/service/src/main/java/dk/kvalitetsit/hello/service/HelloServiceImpl.java @@ -0,0 +1,13 @@ +package dk.kvalitetsit.hello.service; + +import dk.kvalitetsit.hello.service.model.HelloServiceInput; +import dk.kvalitetsit.hello.service.model.HelloServiceOutput; + +import java.time.ZonedDateTime; + +public class HelloServiceImpl implements HelloService { + @Override + public HelloServiceOutput helloServiceBusinessLogic(HelloServiceInput input) { + return new HelloServiceOutput(input.name(), ZonedDateTime.now()); + } +} diff --git a/service/src/main/java/dk/kvalitetsit/hello/service/model/HelloServiceInput.java b/service/src/main/java/dk/kvalitetsit/hello/service/model/HelloServiceInput.java new file mode 100644 index 0000000..7c5e6d5 --- /dev/null +++ b/service/src/main/java/dk/kvalitetsit/hello/service/model/HelloServiceInput.java @@ -0,0 +1,4 @@ +package dk.kvalitetsit.hello.service.model; + +public record HelloServiceInput(String name) { +} diff --git a/service/src/main/java/dk/kvalitetsit/hello/service/model/HelloServiceOutput.java b/service/src/main/java/dk/kvalitetsit/hello/service/model/HelloServiceOutput.java new file mode 100644 index 0000000..5ef82e1 --- /dev/null +++ b/service/src/main/java/dk/kvalitetsit/hello/service/model/HelloServiceOutput.java @@ -0,0 +1,6 @@ +package dk.kvalitetsit.hello.service.model; + +import java.time.ZonedDateTime; + +public record HelloServiceOutput(String name, ZonedDateTime now) { +} diff --git a/service/src/main/resources/db/migration/V10__create_hello_table.sql b/service/src/main/resources/db/migration/V10__create_hello_table.sql new file mode 100644 index 0000000..dfabed6 --- /dev/null +++ b/service/src/main/resources/db/migration/V10__create_hello_table.sql @@ -0,0 +1,6 @@ +CREATE TABLE hello_table ( + id bigint(20) NOT NULL AUTO_INCREMENT, + name varchar(100) not null, + PRIMARY KEY (id) +) +; diff --git a/service/src/test/java/dk/kvalitetsit/hello/configuration/TestConfiguration.java b/service/src/test/java/dk/kvalitetsit/hello/configuration/TestConfiguration.java new file mode 100644 index 0000000..bccb73e --- /dev/null +++ b/service/src/test/java/dk/kvalitetsit/hello/configuration/TestConfiguration.java @@ -0,0 +1,20 @@ +package dk.kvalitetsit.hello.configuration; + +import dk.kvalitetsit.hello.dao.HelloDao; +import dk.kvalitetsit.hello.dao.HelloDaoImpl; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.sql.DataSource; + +@Configuration +@EnableAutoConfiguration +public class TestConfiguration { + // Configure beans used for test + + @Bean + public HelloDao helloDao(DataSource dataSource) { + return new HelloDaoImpl(dataSource); + } +} diff --git a/service/src/test/java/dk/kvalitetsit/hello/controller/HelloControllerTest.java b/service/src/test/java/dk/kvalitetsit/hello/controller/HelloControllerTest.java new file mode 100644 index 0000000..7014a37 --- /dev/null +++ b/service/src/test/java/dk/kvalitetsit/hello/controller/HelloControllerTest.java @@ -0,0 +1,48 @@ +package dk.kvalitetsit.hello.controller; + +import dk.kvalitetsit.hello.service.HelloService; +import dk.kvalitetsit.hello.service.model.HelloServiceInput; +import dk.kvalitetsit.hello.service.model.HelloServiceOutput; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import java.time.ZonedDateTime; +import java.util.UUID; + +import static junit.framework.TestCase.assertNotNull; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.times; + +public class HelloControllerTest { + private HelloController helloController; + private HelloService helloService; + + @Before + public void setup() { + helloService = Mockito.mock(HelloService.class); + + helloController = new HelloController(helloService); + } + + @Test + public void testCallController() { + var name = UUID.randomUUID().toString(); + + var expectedDate = ZonedDateTime.now(); + Mockito.when(helloService.helloServiceBusinessLogic(Mockito.any())).then(a -> new HelloServiceOutput(a.getArgument(0, HelloServiceInput.class).name(), expectedDate)); + + var result = helloController.v1HelloGet(name); + + assertNotNull(result); + assertEquals(name, result.getBody().getName()); + assertEquals(expectedDate.toOffsetDateTime(), result.getBody().getNow()); + + var inputArgumentCaptor = ArgumentCaptor.forClass(HelloServiceInput.class); + Mockito.verify(helloService, times(1)).helloServiceBusinessLogic(inputArgumentCaptor.capture()); + + assertNotNull(inputArgumentCaptor.getValue()); + assertEquals(name, inputArgumentCaptor.getValue().name()); + } +} diff --git a/service/src/test/java/dk/kvalitetsit/hello/dao/AbstractDaoTest.java b/service/src/test/java/dk/kvalitetsit/hello/dao/AbstractDaoTest.java new file mode 100644 index 0000000..fe61a55 --- /dev/null +++ b/service/src/test/java/dk/kvalitetsit/hello/dao/AbstractDaoTest.java @@ -0,0 +1,44 @@ +package dk.kvalitetsit.hello.dao; + +import dk.kvalitetsit.hello.configuration.TestConfiguration; +import dk.kvalitetsit.hello.configuration.DatabaseConfiguration; +import org.junit.BeforeClass; +import org.junit.runner.RunWith; +import org.springframework.context.annotation.PropertySource; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; +import org.springframework.transaction.annotation.Transactional; +import org.testcontainers.containers.MariaDBContainer; + +@RunWith(SpringJUnit4ClassRunner.class) +@PropertySource("test.properties") +@ContextConfiguration( + classes = { TestConfiguration.class, DatabaseConfiguration.class}, + loader = AnnotationConfigContextLoader.class) +@Transactional +abstract public class AbstractDaoTest { + private static Object initialized = null; + + @BeforeClass + public static void setupMariadbJdbcUrl() { + if (initialized == null) { + var username = "hellouser"; + var password = "secret1234"; + + MariaDBContainer mariadb = new MariaDBContainer("mariadb:10.6") + .withDatabaseName("hellodb") + .withUsername(username) + .withPassword(password); + mariadb.start(); + + String jdbcUrl = mariadb.getJdbcUrl(); + System.setProperty("jdbc.url", jdbcUrl); + System.setProperty("jdbc.user", username); + System.setProperty("jdbc.pass", password); + + initialized = new Object(); + } + } +} + diff --git a/service/src/test/java/dk/kvalitetsit/hello/dao/HelloDaoTest.java b/service/src/test/java/dk/kvalitetsit/hello/dao/HelloDaoTest.java new file mode 100644 index 0000000..d47256e --- /dev/null +++ b/service/src/test/java/dk/kvalitetsit/hello/dao/HelloDaoTest.java @@ -0,0 +1,27 @@ +package dk.kvalitetsit.hello.dao; + +import dk.kvalitetsit.hello.dao.entity.HelloEntity; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.UUID; + +import static org.junit.Assert.*; + +public class HelloDaoTest extends AbstractDaoTest { + @Autowired + private HelloDao helloDao; + + @Test + public void testByMessageId() { + var input = HelloEntity.createInstance(UUID.randomUUID().toString()); + + helloDao.insert(input); + + var result = helloDao.findAll(); + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(input.name(), result.get(0).name()); + assertNotNull(result.get(0).id()); + } +} diff --git a/service/src/test/java/dk/kvalitetsit/hello/service/HelloServiceImplTest.java b/service/src/test/java/dk/kvalitetsit/hello/service/HelloServiceImplTest.java new file mode 100644 index 0000000..43a8bdf --- /dev/null +++ b/service/src/test/java/dk/kvalitetsit/hello/service/HelloServiceImplTest.java @@ -0,0 +1,29 @@ +package dk.kvalitetsit.hello.service; + +import dk.kvalitetsit.hello.service.model.HelloServiceInput; +import org.junit.Before; +import org.junit.Test; + +import java.util.UUID; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class HelloServiceImplTest { + private HelloService helloService; + + @Before + public void setup() { + helloService = new HelloServiceImpl(); + } + + @Test + public void testValidInput() { + var input = new HelloServiceInput(UUID.randomUUID().toString()); + + var result = helloService.helloServiceBusinessLogic(input); + assertNotNull(result); + assertNotNull(result.now()); + assertEquals(input.name(), result.name()); + } +} diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..79bc0ed --- /dev/null +++ b/setup.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +if ! command -v tree &> /dev/null; then + echo "Command 'tree' not found. Please install tree." + exit 1 +fi + +if [ -z $1 ]; then + echo "Run setup.sh REPOSITORY_NAME" + exit 1 +fi + +ignore_files=".git|setup.sh" + +for input_file in `tree -I "${ignore_files}" -Ffai --noreport` +do + if [ ! -d "${input_file}" ]; then + echo "Processing file: ${input_file}" + sed -i -e "s/kithugs/$1/g" "${input_file}" + fi +done + +# Clean up / implode +rm setup.sh \ No newline at end of file diff --git a/testreport/pom.xml b/testreport/pom.xml new file mode 100644 index 0000000..e652988 --- /dev/null +++ b/testreport/pom.xml @@ -0,0 +1,48 @@ + + + + kithugs + dk.kvalitetsit.kithugs + 0.0.1-SNAPSHOT + + 4.0.0 + + testreport + + + + dk.kvalitetsit.kithugs + service + + + + dk.kvalitetsit.kithugs + integrationtest + + + + dk.kvalitetsit.kithugs + web + + + + + + + org.jacoco + jacoco-maven-plugin + + + report-aggregate + verify + + report-aggregate + + + + + + + \ No newline at end of file diff --git a/web/docker/Dockerfile b/web/docker/Dockerfile new file mode 100644 index 0000000..e9ed22c --- /dev/null +++ b/web/docker/Dockerfile @@ -0,0 +1,18 @@ +FROM ibm-semeru-runtimes:open-17-jre + +RUN apt-get update && apt-get install -y tzdata + +RUN groupadd --gid 11000 appuser && \ + useradd --gid 11000 --uid 11001 -m appuser + +WORKDIR /home/appuser +COPY entrypoint.sh . + +COPY config/logback.xml logback-spring.xml +COPY /maven/web-exec.jar web.jar + +EXPOSE 8080 +EXPOSE 8081 + +USER appuser +ENTRYPOINT ["/home/appuser/entrypoint.sh"] \ No newline at end of file diff --git a/web/docker/config/logback.xml b/web/docker/config/logback.xml new file mode 100644 index 0000000..b79923f --- /dev/null +++ b/web/docker/config/logback.xml @@ -0,0 +1,30 @@ + + + + + + + + + { + "logger": "%logger", + "level": "%level", + "correlation-id": "%X{correlation-id}", + "thread": "%thread", + "message": "%m" + } + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/docker/entrypoint.sh b/web/docker/entrypoint.sh new file mode 100755 index 0000000..0f16b2b --- /dev/null +++ b/web/docker/entrypoint.sh @@ -0,0 +1,34 @@ +#! /bin/bash + +if [ "$TZ" = "" ] +then + echo "Using default timezone (UTC)" +fi + +if [[ -z $LOGGING_CONFIG ]]; then + echo "Default logback configuration file: /home/appuser/logback-spring.xml" + export LOGGING_CONFIG=/home/appuser/logback-spring.xml +fi + +if [[ -z $LOG_LEVEL ]]; then + echo "Default LOG_LEVEL = INFO" + export LOG_LEVEL=INFO +fi + +if [[ -z $LOG_LEVEL_FRAMEWORK ]]; then + echo "Default LOG_LEVEL_FRAMEWORK = INFO" + export LOG_LEVEL_FRAMEWORK=INFO +fi + +if [[ -z $CORRELATION_ID ]]; then + echo "Default CORRELATION_ID = correlation-id" + export CORRELATION_ID=x-request-id +fi + +JAR_FILE=web.jar + +echo "Starting service with the following command." +echo "java $JVM_OPTS -jar $JAR_FILE" + +# start the application +exec java $JVM_OPTS -jar $JAR_FILE diff --git a/web/pom.xml b/web/pom.xml new file mode 100644 index 0000000..f0ff6f6 --- /dev/null +++ b/web/pom.xml @@ -0,0 +1,102 @@ + + + + kithugs + dk.kvalitetsit.kithugs + 0.0.1-SNAPSHOT + + 4.0.0 + + web + + + + dk.kvalitetsit.kithugs + service + + + + org.springframework.boot + spring-boot-starter-web + + + + net.logstash.logback + logstash-logback-encoder + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + exec + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + . + ./lib/ + + + + + + + io.fabric8 + docker-maven-plugin + + + + kvalitetsit/kithugs + + + latest + dev + ${git.commit.id} + + ${basedir}/docker + + + + + ${project.build.directory}/web-exec.jar + + + + artifact + + + + + + + + build-image + install + + build + + + + + + + \ No newline at end of file diff --git a/web/src/main/java/dk/kvalitetsit/hello/Application.java b/web/src/main/java/dk/kvalitetsit/hello/Application.java new file mode 100644 index 0000000..a833cb2 --- /dev/null +++ b/web/src/main/java/dk/kvalitetsit/hello/Application.java @@ -0,0 +1,11 @@ +package dk.kvalitetsit.hello; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/web/src/test/java/dk/kvalitetsit/hello/ApplicationTests.java b/web/src/test/java/dk/kvalitetsit/hello/ApplicationTests.java new file mode 100644 index 0000000..6b46805 --- /dev/null +++ b/web/src/test/java/dk/kvalitetsit/hello/ApplicationTests.java @@ -0,0 +1,14 @@ +package dk.kvalitetsit.hello; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +@Disabled +class ApplicationTests { + + @Test + void contextLoads() { + } +} diff --git a/web/src/test/resources/application.properties b/web/src/test/resources/application.properties new file mode 100644 index 0000000..17666cf --- /dev/null +++ b/web/src/test/resources/application.properties @@ -0,0 +1,9 @@ +video.api.endpoint=http://localhost + +static.files.path=/start +background.sub.path=path +background.name=name +style.sub.path=another_path +style.name=another_name +video.portal=portal +organisation.api.endpoint=http://localhost \ No newline at end of file