From 40eeff17edc80aee6559adcf91f19b3057801538 Mon Sep 17 00:00:00 2001 From: rezayaghobi-dev Date: Mon, 10 Aug 2026 20:50:41 +0330 Subject: [PATCH] added mono-repo voting app CI/CD --- .../voting-app-monorepo-reference/.gitignore | 3 + .../.gitlab-ci.yml | 183 ++++++++++++++++++ .../.trivyignore | 8 + .../docker-compose.deploy.yml | 39 ++++ 4 files changed, 233 insertions(+) create mode 100644 docker-compose/gitlab/voting-app-monorepo-reference/.gitignore create mode 100644 docker-compose/gitlab/voting-app-monorepo-reference/.gitlab-ci.yml create mode 100644 docker-compose/gitlab/voting-app-monorepo-reference/.trivyignore create mode 100644 docker-compose/gitlab/voting-app-monorepo-reference/docker-compose.deploy.yml diff --git a/docker-compose/gitlab/voting-app-monorepo-reference/.gitignore b/docker-compose/gitlab/voting-app-monorepo-reference/.gitignore new file mode 100644 index 0000000..53a89d1 --- /dev/null +++ b/docker-compose/gitlab/voting-app-monorepo-reference/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +*.log +.env diff --git a/docker-compose/gitlab/voting-app-monorepo-reference/.gitlab-ci.yml b/docker-compose/gitlab/voting-app-monorepo-reference/.gitlab-ci.yml new file mode 100644 index 0000000..bf097af --- /dev/null +++ b/docker-compose/gitlab/voting-app-monorepo-reference/.gitlab-ci.yml @@ -0,0 +1,183 @@ +stages: + - build + - test + - scan + - deploy + - load-test + - backup +.build-service: + stage: build + image: docker:27-cli + services: [] # host-socket build, same pattern as ci-images — no DinD + before_script: + - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY" + script: + - docker build --pull -t "$CI_REGISTRY_IMAGE/$SERVICE_NAME:latest" -t "$CI_REGISTRY_IMAGE/$SERVICE_NAME:$CI_COMMIT_SHORT_SHA" "$SERVICE_NAME" + - docker push "$CI_REGISTRY_IMAGE/$SERVICE_NAME:latest" + - docker push "$CI_REGISTRY_IMAGE/$SERVICE_NAME:$CI_COMMIT_SHORT_SHA" + +build-vote: + extends: .build-service + variables: + SERVICE_NAME: vote + +build-result: + extends: .build-service + variables: + SERVICE_NAME: result + +build-worker: + extends: .build-service + variables: + SERVICE_NAME: worker + +test-vote: + stage: test + image: docker:27-cli + services: [] + script: + - docker network create test-net-$CI_JOB_ID + - docker run -d --name vote-test-$CI_JOB_ID --network test-net-$CI_JOB_ID "$CI_REGISTRY_IMAGE/vote:$CI_COMMIT_SHORT_SHA" + - sleep 5 + - docker run --rm --network test-net-$CI_JOB_ID curlimages/curl:latest curl -sf "http://vote-test-$CI_JOB_ID:80/" + after_script: + - docker rm -f vote-test-$CI_JOB_ID || true + - docker network rm test-net-$CI_JOB_ID || true + +test-result: + stage: test + image: docker:27-cli + services: [] + script: + - docker network create test-net-$CI_JOB_ID + - docker run -d --name result-test-$CI_JOB_ID --network test-net-$CI_JOB_ID "$CI_REGISTRY_IMAGE/result:$CI_COMMIT_SHORT_SHA" + - sleep 5 + - docker run --rm --network test-net-$CI_JOB_ID curlimages/curl:latest curl -sf "http://result-test-$CI_JOB_ID:80/" + after_script: + - docker rm -f result-test-$CI_JOB_ID || true + - docker network rm test-net-$CI_JOB_ID || true + +test-worker: + stage: test + image: docker:27-cli + services: [] + script: + - docker run -d --name worker-test-$CI_JOB_ID "$CI_REGISTRY_IMAGE/worker:$CI_COMMIT_SHORT_SHA" + - sleep 5 + - | + STATUS=$(docker inspect -f '{{.State.Running}}' worker-test-$CI_JOB_ID) + if [ "$STATUS" != "true" ]; then + echo "worker exited unexpectedly on startup" + docker logs worker-test-$CI_JOB_ID + exit 1 + fi + after_script: + - docker rm -f worker-test-$CI_JOB_ID || true +.scan-service: + stage: scan + image: + name: aquasec/trivy:latest + entrypoint: [""] + variables: + TRIVY_DB_REPOSITORY: "ghcr.io/aquasecurity/trivy-db:2" + TRIVY_CACHE_DIR: ".trivycache/" + cache: + key: trivy-db-cache + paths: + - .trivycache/ + script: + - trivy image --exit-code 1 --ignore-unfixed --severity CRITICAL,HIGH --format table "$CI_REGISTRY_IMAGE/$SERVICE_NAME:$CI_COMMIT_SHORT_SHA" +scan-vote: + extends: .scan-service + variables: + SERVICE_NAME: vote + +scan-result: + extends: .scan-service + variables: + SERVICE_NAME: result + +scan-worker: + extends: .scan-service + variables: + SERVICE_NAME: worker + +deploy-dev: + stage: deploy + image: docker:27-cli + services: [] + variables: + IMAGE_TAG: "$CI_COMMIT_SHORT_SHA" + VOTE_PORT: "5100" + RESULT_PORT: "5101" + environment: + name: dev + script: + - docker compose -p voting-dev -f docker-compose.deploy.yml up -d + - sleep 10 + - docker compose -p voting-dev -f docker-compose.deploy.yml ps + - docker compose -p voting-dev -f docker-compose.deploy.yml exec -T vote curl -sf http://localhost:80/ + - docker compose -p voting-dev -f docker-compose.deploy.yml exec -T result curl -sf http://localhost:80/ + after_script: + - docker compose -p voting-dev -f docker-compose.deploy.yml down -v + +deploy-staging: + stage: deploy + image: docker:27-cli + services: [] + variables: + IMAGE_TAG: "$CI_COMMIT_SHORT_SHA" + VOTE_PORT: "5200" + RESULT_PORT: "5201" + environment: + name: staging + url: "http://192.168.100.6:5200" + when: manual + script: + - docker compose -p voting-staging -f docker-compose.deploy.yml up -d + - sleep 10 + - docker compose -p voting-staging -f docker-compose.deploy.yml exec -T vote curl -sf http://localhost:80/ + - docker compose -p voting-staging -f docker-compose.deploy.yml exec -T result curl -sf http://localhost:80/ +deploy-prod: + stage: deploy + image: docker:27-cli + services: [] + variables: + IMAGE_TAG: "$CI_COMMIT_SHORT_SHA" + VOTE_PORT: "5300" + RESULT_PORT: "5301" + environment: + name: production + url: "http://192.168.100.6:5300" + when: manual + script: + - docker compose -p voting-prod -f docker-compose.deploy.yml up -d + - sleep 10 + - docker compose -p voting-prod -f docker-compose.deploy.yml exec -T vote curl -sf http://localhost:80/ + - docker compose -p voting-prod -f docker-compose.deploy.yml exec -T result curl -sf http://localhost:80/ +load-test-staging: + stage: load-test + image: + name: grafana/k6:latest + entrypoint: [""] + when: manual + needs: ["deploy-staging"] + script: + - k6 run -e TARGET_HOST=192.168.100.6 -e VOTE_PORT=5200 loadtest/vote-load-test.js +backup-prod-db: + stage: backup + image: docker:27-cli + services: [] + when: manual + needs: ["deploy-prod"] + script: + - export BACKUP_FILE="voting-prod-$(date +%Y%m%d-%H%M%S).sql.gz" + - docker exec voting-prod-db-1 pg_dump -U postgres postgres | gzip > "$BACKUP_FILE" + - docker create --name mc-helper-$CI_JOB_ID --entrypoint sh minio/mc:RELEASE.2025-08-13T08-35-41Z-cpuv1 -c "sleep 300" + - docker start mc-helper-$CI_JOB_ID + - docker cp "$BACKUP_FILE" mc-helper-$CI_JOB_ID:/tmp/"$BACKUP_FILE" + - docker exec mc-helper-$CI_JOB_ID mc alias set localminio http://192.168.100.6:9500 "$MINIO_ACCESS_KEY" "$MINIO_SECRET_KEY" + - docker exec mc-helper-$CI_JOB_ID mc cp /tmp/"$BACKUP_FILE" localminio/postgres-backups/ + - docker exec mc-helper-$CI_JOB_ID mc ls localminio/postgres-backups/ + after_script: + - docker rm -f mc-helper-$CI_JOB_ID || true diff --git a/docker-compose/gitlab/voting-app-monorepo-reference/.trivyignore b/docker-compose/gitlab/voting-app-monorepo-reference/.trivyignore new file mode 100644 index 0000000..5d917eb --- /dev/null +++ b/docker-compose/gitlab/voting-app-monorepo-reference/.trivyignore @@ -0,0 +1,8 @@ +# jaraco.context / wheel are vendored copies bundled inside setuptools, +# not direct app dependencies. Fixing requires upgrading pip/setuptools, +# which needs files.pythonhosted.org — currently unreachable from this +# network (confirmed: pypi.org resolves fine, files.pythonhosted.org +# times out). Revisit when connectivity allows, or if these ever appear +# on the app's real runtime dependency surface. +CVE-2026-23949 +CVE-2026-24049 diff --git a/docker-compose/gitlab/voting-app-monorepo-reference/docker-compose.deploy.yml b/docker-compose/gitlab/voting-app-monorepo-reference/docker-compose.deploy.yml new file mode 100644 index 0000000..c9b2586 --- /dev/null +++ b/docker-compose/gitlab/voting-app-monorepo-reference/docker-compose.deploy.yml @@ -0,0 +1,39 @@ +services: + redis: + image: redis:7-alpine + mem_limit: 64m + + db: + image: postgres:15-alpine + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + volumes: + - db-data:/var/lib/postgresql/data + mem_limit: 128m + + vote: + image: "${CI_REGISTRY_IMAGE}/vote:${IMAGE_TAG}" + ports: + - "${VOTE_PORT}:80" + depends_on: + - redis + mem_limit: 96m + + result: + image: "${CI_REGISTRY_IMAGE}/result:${IMAGE_TAG}" + ports: + - "${RESULT_PORT}:80" + depends_on: + - db + mem_limit: 96m + + worker: + image: "${CI_REGISTRY_IMAGE}/worker:${IMAGE_TAG}" + depends_on: + - redis + - db + mem_limit: 128m + +volumes: + db-data: