From 2d2c214db1198bbfb3655ec7b8a325b5e15077a7 Mon Sep 17 00:00:00 2001 From: Toksi Date: Wed, 22 Apr 2026 11:56:34 +0500 Subject: [PATCH] =?UTF-8?q?=D0=A3=D1=81=D0=B8=D0=BB=D0=B5=D0=BD=20deploy?= =?UTF-8?q?=20path=20dev/prod=20=D0=B8=20=D1=83=D0=B1=D1=80=D0=B0=D0=BD=20?= =?UTF-8?q?legacy=20startup=20flow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/dev-ci.yml | 46 +++++++++++- .github/workflows/release-ci.yml | 118 ++++++++++++++++++++----------- Dockerfile | 11 ++- docker-compose.dev-ci.yml | 2 +- docker-compose.prod-ci.yml | 2 +- docker-compose.yml | 9 ++- scripts/startup.sh | 7 -- 7 files changed, 141 insertions(+), 54 deletions(-) delete mode 100644 scripts/startup.sh diff --git a/.github/workflows/dev-ci.yml b/.github/workflows/dev-ci.yml index bfd6abab..f159a7f8 100644 --- a/.github/workflows/dev-ci.yml +++ b/.github/workflows/dev-ci.yml @@ -51,6 +51,7 @@ jobs: docker compose -f docker-compose.dev-ci.yml config >/dev/null docker compose -f docker-compose.dev-ci.yml build web && + docker compose -f docker-compose.dev-ci.yml run --rm web python manage.py migrate && docker compose -f docker-compose.dev-ci.yml up -d --force-recreate --remove-orphans && install -d /etc/nginx/procollab/includes && @@ -63,4 +64,47 @@ jobs: else sudo nginx -t && sudo systemctl reload nginx - fi + fi && + + for attempt in $(seq 1 24); do + root_status="$(curl -s -o /dev/null -w '%{http_code}' https://dev.procollab.ru/ || true)" && + admin_status="$(curl -s -o /dev/null -w '%{http_code}' https://dev.procollab.ru/admin/login/ || true)" && + + if [ "$root_status" = "401" ] && [ "$admin_status" = "200" ]; then + echo "Smoke check passed on attempt ${attempt}" && + break + fi + + sleep 5 + done && + + if [ "$root_status" != "401" ] || [ "$admin_status" != "200" ]; then + echo "Smoke check failed: /=${root_status} /admin/login/=${admin_status}" >&2 && + exit 1 + fi && + + celery_status="" && + celery_ping="" && + for attempt in $(seq 1 24); do + celery_status="$(docker inspect -f '{{.State.Status}}' api_celery 2>/dev/null || true)" && + if [ "$celery_status" = "running" ]; then + celery_ping="$(docker compose -f docker-compose.dev-ci.yml exec -T celerys sh -lc 'celery -A procollab inspect ping -d \"celery@$(hostname)\"' 2>&1 || true)" && + printf '%s\n' "$celery_ping" && + if printf '%s\n' "$celery_ping" | grep -q 'pong'; then + echo "Celery check passed on attempt ${attempt}" && + break + fi + fi && + + sleep 5 + done && + + if [ "$celery_status" != "running" ]; then + echo "Celery container is not running: ${celery_status}" >&2 && + exit 1 + fi && + + printf '%s\n' "$celery_ping" | grep -q 'pong' || { + echo "Celery ping failed" >&2 + exit 1 + } diff --git a/.github/workflows/release-ci.yml b/.github/workflows/release-ci.yml index 9ed4b5e7..5389e3d4 100644 --- a/.github/workflows/release-ci.yml +++ b/.github/workflows/release-ci.yml @@ -15,11 +15,45 @@ on: type: string jobs: + prepare: + name: Resolve Release Ref + runs-on: ubuntu-latest + outputs: + image_tag: ${{ steps.vars.outputs.image_tag }} + deploy_ref: ${{ steps.vars.outputs.deploy_ref }} + steps: + - name: Resolve image tag and deploy ref + id: vars + run: | + if [ "${{ github.event_name }}" = "release" ]; then + image_tag='${{ github.event.release.tag_name }}' + deploy_ref='${{ github.event.release.tag_name }}' + else + image_tag='${{ github.event.inputs.image_tag }}' + deploy_ref='${{ github.event.inputs.deploy_ref }}' + fi + + if [ -z "$image_tag" ]; then + echo "IMAGE_TAG is empty" >&2 + exit 1 + fi + + if [ -z "$deploy_ref" ]; then + echo "DEPLOY_REF is empty" >&2 + exit 1 + fi + + echo "image_tag=$image_tag" >> "$GITHUB_OUTPUT" + echo "deploy_ref=$deploy_ref" >> "$GITHUB_OUTPUT" + test: name: Tests runs-on: ubuntu-latest + needs: [ prepare ] steps: - uses: actions/checkout@v3 + with: + ref: ${{ needs.prepare.outputs.deploy_ref }} - name: Set up Python 3.11 uses: actions/setup-python@v4 @@ -57,39 +91,15 @@ jobs: build: name: Build Image runs-on: ubuntu-latest - needs: [ test ] + needs: [ prepare, test ] outputs: - image_tag: ${{ steps.vars.outputs.image_tag }} - deploy_ref: ${{ steps.vars.outputs.deploy_ref }} + image_tag: ${{ needs.prepare.outputs.image_tag }} + deploy_ref: ${{ needs.prepare.outputs.deploy_ref }} steps: - name: "Checkout repository" uses: actions/checkout@v3 with: - ref: ${{ github.event_name == 'release' && github.event.release.tag_name || github.event.inputs.deploy_ref }} - - - name: Resolve image tag - id: vars - run: | - if [ "${{ github.event_name }}" = "release" ]; then - image_tag='${{ github.event.release.tag_name }}' - deploy_ref='${{ github.event.release.tag_name }}' - else - image_tag='${{ github.event.inputs.image_tag }}' - deploy_ref='${{ github.event.inputs.deploy_ref }}' - fi - - if [ -z "$image_tag" ]; then - echo "IMAGE_TAG is empty" >&2 - exit 1 - fi - - if [ -z "$deploy_ref" ]; then - echo "DEPLOY_REF is empty" >&2 - exit 1 - fi - - echo "image_tag=$image_tag" >> "$GITHUB_OUTPUT" - echo "deploy_ref=$deploy_ref" >> "$GITHUB_OUTPUT" + ref: ${{ needs.prepare.outputs.deploy_ref }} - name: "Set up QEMU" uses: docker/setup-qemu-action@v3 @@ -110,7 +120,7 @@ jobs: with: images: ghcr.io/procollab-github/api tags: | - type=raw,value=${{ steps.vars.outputs.image_tag }} + type=raw,value=${{ needs.prepare.outputs.image_tag }} - name: Build and push container uses: docker/build-push-action@v5 with: @@ -124,7 +134,7 @@ jobs: deploy: name: Deploy runs-on: ubuntu-latest - needs: [ build ] + needs: [ prepare, build ] steps: - name: Deploy to server uses: garygrossgarten/github-action-ssh@release @@ -135,8 +145,8 @@ jobs: command: | set -eu - export IMAGE_TAG="${{ needs.build.outputs.image_tag }}" - export DEPLOY_REF="${{ needs.build.outputs.deploy_ref }}" + export IMAGE_TAG="${{ needs.prepare.outputs.image_tag }}" + export DEPLOY_REF="${{ needs.prepare.outputs.deploy_ref }}" echo "Deploying IMAGE_TAG=${IMAGE_TAG} from DEPLOY_REF=${DEPLOY_REF}" if [ "$(id -un)" = "app" ]; then @@ -150,10 +160,6 @@ jobs: fi cd /home/app/procollab-backend - docker container prune -f - docker image prune -a -f - docker compose -f docker-compose.prod-ci.yml -p prod pull - rm -f .env touch .env @@ -175,7 +181,9 @@ jobs: chmod 600 .env docker compose -f docker-compose.prod-ci.yml -p prod config >/dev/null + docker compose -f docker-compose.prod-ci.yml -p prod pull + docker compose -f docker-compose.prod-ci.yml -p prod run --rm web python manage.py migrate docker compose -f docker-compose.prod-ci.yml -p prod up -d --remove-orphans if [ "$(id -u)" -eq 0 ]; then nginx -t @@ -186,16 +194,44 @@ jobs: fi for attempt in $(seq 1 24); do - root_status="$(curl -k -s -o /dev/null -w '%{http_code}' https://api.procollab.ru/ || true)" - admin_status="$(curl -k -s -o /dev/null -w '%{http_code}' https://api.procollab.ru/admin/login/ || true)" + root_status="$(curl -s -o /dev/null -w '%{http_code}' https://api.procollab.ru/ || true)" + admin_status="$(curl -s -o /dev/null -w '%{http_code}' https://api.procollab.ru/admin/login/ || true)" if [ "$root_status" = "401" ] && [ "$admin_status" = "200" ]; then echo "Smoke check passed on attempt ${attempt}" - exit 0 + break + fi + + sleep 5 + done + + if [ "$root_status" != "401" ] || [ "$admin_status" != "200" ]; then + echo "Smoke check failed: /=${root_status} /admin/login/=${admin_status}" >&2 + exit 1 + fi + + celery_status="" + celery_ping="" + for attempt in $(seq 1 24); do + celery_status="$(docker inspect -f '{{.State.Status}}' api_celery 2>/dev/null || true)" + if [ "$celery_status" = "running" ]; then + celery_ping="$(docker compose -f docker-compose.prod-ci.yml -p prod exec -T celerys sh -lc 'celery -A procollab inspect ping -d \"celery@$(hostname)\"' 2>&1 || true)" + printf '%s\n' "$celery_ping" + if printf '%s\n' "$celery_ping" | grep -q 'pong'; then + echo "Celery check passed on attempt ${attempt}" + break + fi fi sleep 5 done - echo "Smoke check failed: /=${root_status} /admin/login/=${admin_status}" - exit 1 + if [ "$celery_status" != "running" ]; then + echo "Celery container is not running: ${celery_status}" >&2 + exit 1 + fi + + printf '%s\n' "$celery_ping" | grep -q 'pong' || { + echo "Celery ping failed" >&2 + exit 1 + } diff --git a/Dockerfile b/Dockerfile index 516d3605..282824c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,5 +27,12 @@ RUN mkdir /procollab/static COPY . /procollab/ -CMD ["bash", "./scripts/startup.sh"] - +RUN DJANGO_SECRET_KEY=build-time-secret \ + DATABASE_NAME=postgres \ + DATABASE_USER=postgres \ + DATABASE_PASSWORD=postgres \ + DATABASE_HOST=localhost \ + DATABASE_PORT=5432 \ + python manage.py collectstatic --no-input + +CMD ["daphne", "-b", "0.0.0.0", "-p", "8000", "procollab.asgi:application"] diff --git a/docker-compose.dev-ci.yml b/docker-compose.dev-ci.yml index 0271744a..782e64ac 100644 --- a/docker-compose.dev-ci.yml +++ b/docker-compose.dev-ci.yml @@ -17,7 +17,7 @@ services: - "127.0.0.1:8000:8000" redis: - image: redis:latest + image: redis:7.2.5 restart: unless-stopped expose: - 6379 diff --git a/docker-compose.prod-ci.yml b/docker-compose.prod-ci.yml index 6b80ba70..45df8d3f 100644 --- a/docker-compose.prod-ci.yml +++ b/docker-compose.prod-ci.yml @@ -13,7 +13,7 @@ services: ports: - "127.0.0.1:8000:8000" redis: - image: redis:latest + image: redis:7.2.5 restart: unless-stopped expose: - 6379 diff --git a/docker-compose.yml b/docker-compose.yml index b4c108b2..830306c2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,17 @@ +# LEGACY compose path. +# Do not use this file for prod or CI/CD deployments. +# Intentional manual use requires: docker compose --profile legacy ... version: '3.9' services: web: + profiles: ["legacy"] container_name: web build: context: . dockerfile: ./Dockerfile restart: always - command: bash ./scripts/startup.sh + command: ["daphne", "-b", "0.0.0.0", "-p", "8000", "procollab.asgi:application"] volumes: - ./log:/procollab/log env_file: @@ -18,6 +22,7 @@ services: - 8000 nginx: + profiles: ["legacy"] container_name: nginx build: ./nginx depends_on: @@ -26,6 +31,7 @@ services: - "8000:80" redis: + profiles: ["legacy"] container_name: redis image: redis:latest expose: @@ -34,6 +40,7 @@ services: - redis-data:/data celerys: + profiles: ["legacy"] container_name: api_celery restart: always build: diff --git a/scripts/startup.sh b/scripts/startup.sh deleted file mode 100644 index 6c35ebf8..00000000 --- a/scripts/startup.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -python manage.py migrate -python manage.py collectstatic --no-input - -# Use Daphne ASGI server instead of Django's dev server. -exec daphne -b 0.0.0.0 -p 8000 procollab.asgi:application