diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000..e12a94bc --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,238 @@ +pipeline { + + agent none + + stages { + + stage('worker-build') { + agent { + docker { + image 'maven:3.9.8-sapmachine-21' + args '-v $HOME/.m2:/root/.m2' + } + + } + when { + changeset '**/worker/**' + } + steps { + echo 'Compiling worker app..' + dir(path: 'worker') { + sh 'mvn compile' + } + + } + } + + stage('worker test') { + agent { + docker { + image 'maven:3.9.8-sapmachine-21' + args '-v $HOME/.m2:/root/.m2' + } + + } + when { + changeset '**/worker/**' + } + steps { + echo 'Running Unit Tets on worker app.' + dir(path: 'worker') { + sh 'mvn clean test' + } + + } + } + + stage('worker-package') { + agent { + docker { + image 'maven:3.9.8-sapmachine-21' + args '-v $HOME/.m2:/root/.m2' + } + + } + when { + branch 'master' + changeset '**/worker/**' + } + steps { + echo 'Packaging worker app' + dir(path: 'worker') { + sh 'mvn package -DskipTests' + archiveArtifacts(artifacts: '**/target/*.jar', fingerprint: true) + } + + } + } + + stage('worker-docker-package') { + agent any + when { + changeset '**/worker/**' + branch 'master' + } + steps { + echo 'Packaging worker app with docker' + script { + docker.withRegistry('https://index.docker.io/v1/', 'dockerlogin') { + def workerImage = docker.build("norahns/worker:v${env.BUILD_ID}", './worker') + workerImage.push() + workerImage.push("${env.BRANCH_NAME}") + workerImage.push('latest') + } + } + + } + } + + stage('result-build') { + agent { + docker { + image 'node:22.4.0-alpine' + } + + } + when { + changeset '**/result/**' + } + steps { + echo 'Compiling result app..' + dir(path: 'result') { + sh 'npm install' + } + + } + } + + stage('result-test') { + agent { + docker { + image 'node:22.4.0-alpine' + } + + } + when { + changeset '**/result/**' + } + steps { + echo 'Running Unit Tests on result app..' + dir(path: 'result') { + sh 'npm install' + sh 'npm test' + } + + } + } + + stage('result-docker-package') { + agent any + when { + changeset '**/result/**' + branch 'master' + } + steps { + echo 'Packaging result app with docker' + script { + docker.withRegistry('https://index.docker.io/v1/', 'dockerlogin') { + def resultImage = docker.build("norahns/result:v${env.BUILD_ID}", './result') + resultImage.push() + resultImage.push("${env.BRANCH_NAME}") + resultImage.push('latest') + } + } + } + } + + stage('vote-build') { + agent { + docker { + image 'python:3.11-slim' + args '--user root' + } + + } + when { + changeset '**/vote/**' + } + steps { + echo 'Compiling vote app.' + dir(path: 'vote') { + sh 'pip install -r requirements.txt' + } + + } + } + + stage('vote-test') { + agent { + docker { + image 'python:3.11-slim' + args '--user root' + } + + } + when { + changeset '**/vote/**' + } + steps { + echo 'Running Unit Tests on vote app.' + dir(path: 'vote') { + sh 'pip install -r requirements.txt' + sh 'nosetests -v' + } + + } + } + + stage('vote-integration') { + agent any + when { + changeset '**/vote/**' + branch 'master' + } + steps { + echo 'Running Integration Tests on vote app' + dir('vote') { + sh 'sh integration_test.sh' + } + } + } + + stage('vote-docker-package') { + agent any + steps { + echo 'Packaging vote app with docker' + script { + docker.withRegistry('https://index.docker.io/v1/', 'dockerlogin') { + // ./vote is the path to the Dockerfile that Jenkins will find from the Github repo + def voteImage = docker.build("norahns/vote:${env.GIT_COMMIT}", "./vote") + voteImage.push() + voteImage.push("${env.BRANCH_NAME}") + voteImage.push("latest") + } + } + + } + } + + + stage('deploy to dev') { + agent any + when { + branch 'master' + } + steps { + echo 'Deploy instavote app with docker compose' + sh 'docker-compose up -d' + } + } + + } + + post { + always { + echo 'Building mono pipeline for voting app is completed.' + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index a8ee7312..584e3917 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +2 TRY + Example Voting App ========= diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 00000000..f96f63e6 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,55 @@ +volumes: + db-data: + +networks: + instavote: + driver: bridge + +services: + vote: + image: norahns/vote:latest + build: ./vote + ports: + - 5000:80 + depends_on: + - redis + networks: + - instavote + + redis: + image: redis:alpine + networks: + - instavote + + db: + image: postgres:15-alpine + environment: + POSTGRES_USER: "postgres" + POSTGRES_PASSWORD: "postgres" + volumes: + - "db-data:/var/lib/postgresql/data" + - "./healthchecks:/healthchecks" + healthcheck: + test: /healthchecks/postgres.sh + interval: "5s" + networks: + - instavote + + result: + image: norahns/result:latest + build: ./result + ports: + - 5001:80 + depends_on: + - db + networks: + - instavote + + worker: + image: norahns/worker:latest + build: ./worker + depends_on: + - redis + - db + networks: + - instavote diff --git a/e2e/.env b/e2e/.env index 100455d5..24b21980 100644 --- a/e2e/.env +++ b/e2e/.env @@ -1,3 +1,3 @@ -VOTE_IMAGE=xxxx/vote:latest -WORKER_IMAGE=xxxx/worker:latest -RESULT_IMAGE=xxxx/result:latest +VOTE_IMAGE=norahns/vote:latest +WORKER_IMAGE=norahns/worker:latest +RESULT_IMAGE=norahns/result:latest diff --git a/e2e/tests/= b/e2e/tests/= new file mode 100644 index 00000000..e69de29b diff --git a/e2e/tests/CACHED b/e2e/tests/CACHED new file mode 100644 index 00000000..e69de29b diff --git a/e2e/tests/Dockerfile b/e2e/tests/Dockerfile index d5a2f8b2..85e00756 100644 --- a/e2e/tests/Dockerfile +++ b/e2e/tests/Dockerfile @@ -9,4 +9,4 @@ RUN apt-get update -qq && apt-get install -qy \ RUN yarn global add phantomjs-prebuilt ADD . /app WORKDIR /app -CMD ./tests.sh +CMD ./tests.sh \ No newline at end of file diff --git a/e2e/tests/[e2e] b/e2e/tests/[e2e] new file mode 100644 index 00000000..e69de29b diff --git a/e2e/tests/exporting b/e2e/tests/exporting new file mode 100644 index 00000000..e69de29b diff --git a/e2e/tests/tests.sh b/e2e/tests/tests.sh index c2811eaa..afc9171e 100755 --- a/e2e/tests/tests.sh +++ b/e2e/tests/tests.sh @@ -2,48 +2,42 @@ # Function to get current vote count get_vote_count() { - phantomjs render.js "http://result:80/" | grep -i vote | cut -d ">" -f 4 | cut -d " " -f1 + # Extracts the number of votes like "7 votes" => "7" + phantomjs render.js "http://result:80/" | grep -oE '[0-9]+ votes' | grep -oE '[0-9]+' } # Wait for services to be ready echo "Waiting for services to be ready..." -while ! timeout 1 bash -c "echo > /dev/tcp/vote/80"; do +while ! timeout 1 bash -c "echo > /dev/tcp/vote/80" 2>/dev/null; do sleep 1 done echo "Vote service is ready." -while ! timeout 1 bash -c "echo > /dev/tcp/result/80"; do +while ! timeout 1 bash -c "echo > /dev/tcp/result/80" 2>/dev/null; do sleep 1 done echo "Result service is ready." # Get initial vote count initial_count=$(get_vote_count) - echo "Initial vote count: $initial_count" - - # Submit first vote echo "Submitting vote (a)..." -curl -sS -X POST --data "vote=a" http://vote +curl -sS -X POST --data "vote=a" http://vote > /dev/null sleep 2 + +# Get vote count after submission current=$(get_vote_count) echo "Vote count after first submission: $current" -# Calculate expected next count -# final=$((initial_count + 1)) -# echo "Expected final count: $final" - -# Check vote count multiple times -# for i in {1..5}; do - # sleep 2 +# Try again if result is unstable or delayed +sleep 2 new=$(get_vote_count) -# done # Final check echo "Performing final check..." -if [ $initial_count -lt $new ]; then +if ! [ -z "$initial_count" ] && ! [ -z "$new" ] && [ "$initial_count" -lt "$new" ]; then echo -e "\e[42m------------" echo -e "\e[92mTests passed" echo -e "\e[42m------------" @@ -52,6 +46,6 @@ else echo -e "\e[41m------------" echo -e "\e[91mTests failed" echo -e "\e[41m------------" - echo "Expected: $next, Actual: $new" + echo "Expected: greater than $initial_count, Actual: $new" exit 1 fi diff --git a/e2e/tests/transferring b/e2e/tests/transferring new file mode 100644 index 00000000..e69de29b diff --git a/result/Dockerfile b/result/Dockerfile new file mode 100644 index 00000000..10eeb5b1 --- /dev/null +++ b/result/Dockerfile @@ -0,0 +1,26 @@ +FROM node:22.4.0-slim + +# Add curl for healthcheck and tini for signal handling +RUN apt-get update && \ + apt-get install -y --no-install-recommends curl tini && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /usr/local/app + +# Have nodemon available for local dev use (file watching) +RUN npm install -g nodemon + +COPY package*.json ./ + +RUN npm ci && \ + npm cache clean --force && \ + mv /usr/local/app/node_modules /node_modules + +COPY . . + +ENV PORT=80 +EXPOSE 80 + +ENTRYPOINT ["/usr/bin/tini", "--"] + +CMD ["node", "server.js"] \ No newline at end of file diff --git a/result/Jenkinsfile b/result/Jenkinsfile new file mode 100644 index 00000000..1af2dd2a --- /dev/null +++ b/result/Jenkinsfile @@ -0,0 +1,84 @@ +pipeline { + agent none + + stages { + stage("build") { + when { + changeset "**/worker/**" + } + agent { + docker { + image 'maven:3.9.8-sapmachine-21' + args '-v $HOME/.m2:/root/.m2' + } + } + steps { + echo 'Compiling worker app..' + dir('worker') { + sh 'mvn compile' + } + } + } + + stage("test") { + when { + changeset "**/worker/**" + } + agent { + docker { + image 'maven:3.9.8-sapmachine-21' + args '-v $HOME/.m2:/root/.m2' + } + } + steps { + echo 'Running Unit Tests on worker app..' + dir('worker') { + sh 'mvn clean test' + } + } + } + + stage("package") { + when { + branch 'master' + changeset "**/worker/**" + } + agent { + docker { + image 'maven:3.9.8-sapmachine-21' + args '-v $HOME/.m2:/root/.m2' + } + } + steps { + echo 'Packaging worker app' + dir('worker') { + sh 'mvn package -DskipTests' + archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true + } + } + } + + stage('docker-package') { + agent any + when { + branch 'master' + changeset "**/result/**" + } + steps { + script { + docker.withRegistry('https://index.docker.io/v1/', 'dockerlogin') { + def resultImage = docker.build("norahns/result:v${env.BUILD_ID}", "./result") + resultImage.push() + resultImage.push("latest") + } + } + } + } + } + + post { + always { + echo 'Building multibranch pipeline for worker is completed..' + } + } +} diff --git a/result/test/mock.test.js b/result/test/mock.test.js index aac87156..716f6f5a 100644 --- a/result/test/mock.test.js +++ b/result/test/mock.test.js @@ -32,6 +32,7 @@ describe('mock test 5', () => { }); }); + describe('mock test 6', () => { it('unit test 6', () => { expect(true).to.be.true; diff --git a/sonar-project.properties b/sonar-project.properties index dcd32fdc..f461c6fb 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,6 +1,6 @@ # Uncomment and update Org matching your configurations on Sonarcloud -sonar.organization=your-org -sonar.projectKey=your-org_example-voting-app +sonar.organization=norahalr +sonar.projectKey=norahalr_LFS261-example-voting-app sonar.projectName=Instavote AIO sonar.projectVersion=1.0 # Comma-separated paths to directories with sources (required) diff --git a/vote/Dockerfile b/vote/Dockerfile new file mode 100644 index 00000000..77877b05 --- /dev/null +++ b/vote/Dockerfile @@ -0,0 +1,32 @@ +# Define a base stage that uses the official python runtime base image +FROM python:3.11-slim AS base + +# Add curl for healthcheck +RUN apt-get update && \ + apt-get install -y --no-install-recommends curl && \ + rm -rf /var/lib/apt/lists/* + +# Set the application directory +WORKDIR /usr/local/app + +# Install our requirements.txt +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Define a stage specifically for development, where it'll watch for filesystem changes +FROM base AS dev +RUN pip install watchdog +ENV FLASK_ENV=development +CMD ["python", "app.py"] + +# Define the final stage that will bundle the application for production +FROM base AS final + +# Copy our code from the current folder to the working directory inside the container +COPY . . + +# Make port 80 available for links and/or publish +EXPOSE 80 + +# Define our command to be run when launching the container +CMD ["gunicorn", "app:app", "-b", "0.0.0.0:80", "--log-file", "-", "--access-logfile", "-", "--workers", "4", "--keep-alive", "0"] \ No newline at end of file diff --git a/vote/Jenkinsfile b/vote/Jenkinsfile index b2fd93e1..bcbc4e6d 100644 --- a/vote/Jenkinsfile +++ b/vote/Jenkinsfile @@ -38,22 +38,22 @@ pipeline { } } - stage('docker-package'){ - agent any - - steps{ - echo 'Packaging wvoteorker app with docker' - script{ - docker.withRegistry('https://index.docker.io/v1/', 'dockerlogin') { - // ./vote is the path to the Dockerfile that Jenkins will find from the Github repo - def voteImage = docker.build("xxxx/vote:v${env.BUILD_ID}", "./vote") - voteImage.push() - voteImage.push("${env.BRANCH_NAME}") - voteImage.push("latest") - } + stage('docker-package') { + agent any + when { + branch 'master' + changeset "**/vote/**" } - } - } + steps { + script { + docker.withRegistry('https://index.docker.io/v1/', 'dockerlogin') { + def voteImage = docker.build("norahns/vote:v${env.BUILD_ID}", "./vote") + voteImage.push() + voteImage.push("latest") + } + } + } + } } post { diff --git a/worker/Dockerfile b/worker/Dockerfile new file mode 100644 index 00000000..fb0b0cbc --- /dev/null +++ b/worker/Dockerfile @@ -0,0 +1,6 @@ +FROM maven:3.9.8-sapmachine-21 +WORKDIR /app +COPY . . +RUN mvn package && \ + mv target/worker-jar-with-dependencies.jar /run/worker.jar && rm -rf /app/* +CMD ["java", "-jar", "/run/worker.jar"] diff --git a/worker/Jenkinsfile b/worker/Jenkinsfile new file mode 100644 index 00000000..47a7e1a3 --- /dev/null +++ b/worker/Jenkinsfile @@ -0,0 +1,89 @@ +pipeline { + + agent none + + stages{ + stage("build"){ + when{ + changeset "**/worker/**" + } + + agent{ + docker{ + image 'maven:3.9.8-sapmachine-21' + args '-v $HOME/.m2:/root/.m2' + } + } + + steps{ + echo 'Compiling worker app..' + dir('worker'){ + sh 'mvn compile' + } + } + } + stage("test"){ + when{ + changeset "**/worker/**" + } + agent{ + docker{ + image 'maven:3.9.8-sapmachine-21' + args '-v $HOME/.m2:/root/.m2' + } + } + steps{ + echo 'Running Unit Tets on worker app..' + dir('worker'){ + sh 'mvn clean test' + } + + } + } + stage("package"){ + when{ + branch 'master' + changeset "**/worker/**" + } + agent{ + docker{ + image 'maven:3.9.8-sapmachine-21' + args '-v $HOME/.m2:/root/.m2' + } + } + steps{ + echo 'Packaging worker app' + dir('worker'){ + sh 'mvn package -DskipTests' + archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true + } + + } + } + + stage('docker-package'){ + agent any + when{ + changeset "**/worker/**" + branch 'master' + } + steps{ + echo 'Packaging worker app with docker' + script{ + docker.withRegistry('https://index.docker.io/v1/', 'dockerlogin') { + def workerImage = docker.build("norahns/worker:v${env.BUILD_ID}", "./worker") + workerImage.push() + workerImage.push("${env.BRANCH_NAME}") + workerImage.push("latest") + } + } + } + } + } + + post{ + always{ + echo 'Building multibranch pipeline for worker is completed..' + } + } +} \ No newline at end of file diff --git a/worker/Jenkinsfile.groovy b/worker/Jenkinsfile.groovy new file mode 100644 index 00000000..47a7e1a3 --- /dev/null +++ b/worker/Jenkinsfile.groovy @@ -0,0 +1,89 @@ +pipeline { + + agent none + + stages{ + stage("build"){ + when{ + changeset "**/worker/**" + } + + agent{ + docker{ + image 'maven:3.9.8-sapmachine-21' + args '-v $HOME/.m2:/root/.m2' + } + } + + steps{ + echo 'Compiling worker app..' + dir('worker'){ + sh 'mvn compile' + } + } + } + stage("test"){ + when{ + changeset "**/worker/**" + } + agent{ + docker{ + image 'maven:3.9.8-sapmachine-21' + args '-v $HOME/.m2:/root/.m2' + } + } + steps{ + echo 'Running Unit Tets on worker app..' + dir('worker'){ + sh 'mvn clean test' + } + + } + } + stage("package"){ + when{ + branch 'master' + changeset "**/worker/**" + } + agent{ + docker{ + image 'maven:3.9.8-sapmachine-21' + args '-v $HOME/.m2:/root/.m2' + } + } + steps{ + echo 'Packaging worker app' + dir('worker'){ + sh 'mvn package -DskipTests' + archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true + } + + } + } + + stage('docker-package'){ + agent any + when{ + changeset "**/worker/**" + branch 'master' + } + steps{ + echo 'Packaging worker app with docker' + script{ + docker.withRegistry('https://index.docker.io/v1/', 'dockerlogin') { + def workerImage = docker.build("norahns/worker:v${env.BUILD_ID}", "./worker") + workerImage.push() + workerImage.push("${env.BRANCH_NAME}") + workerImage.push("latest") + } + } + } + } + } + + post{ + always{ + echo 'Building multibranch pipeline for worker is completed..' + } + } +} \ No newline at end of file diff --git a/worker/src/main/java/worker/Worker.java b/worker/src/main/java/worker/Worker.java index 26e5a8bb..00cbd828 100644 --- a/worker/src/main/java/worker/Worker.java +++ b/worker/src/main/java/worker/Worker.java @@ -123,4 +123,25 @@ static void sleep(long duration) { System.exit(1); } } +static class FizzBuzz { + public static void generate(int limit) { + for (int i = 1; i <= limit; i++) { + if (i % 3 == 0 && i % 5 == 0) { + System.out.println("FizzBuzz"); + } else if (i % 3 == 0) { + System.out.println("Fizz"); + } else if (i % 5 == 0) { + System.out.println("Buzz"); + } else { + System.out.println(i); + } + } + } + + // Example of how it could be called, not part of the main application flow + public static void main(String[] args) { + System.out.println("FizzBuzz demonstration:"); + generate(20); // Generate FizzBuzz up to 20 + } + } }