Release Prod #362
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release Prod | |
| on: | |
| release: | |
| types: [ published ] | |
| workflow_dispatch: | |
| inputs: | |
| image_tag: | |
| description: Docker image tag to build and deploy | |
| required: true | |
| type: string | |
| deploy_ref: | |
| description: Git ref or tag to checkout on the server | |
| required: true | |
| 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 | |
| with: | |
| python-version: 3.11 | |
| - name: cache poetry install | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.local | |
| key: poetry-1.2.2-0 | |
| - uses: snok/install-poetry@v1 | |
| with: | |
| version: 1.2.2 | |
| virtualenvs-create: true | |
| virtualenvs-in-project: true | |
| - name: cache deps | |
| id: cache-deps | |
| uses: actions/cache@v4 | |
| with: | |
| path: .venv | |
| key: pydeps-${{ hashFiles('**/poetry.lock') }} | |
| - run: poetry install | |
| - run: poetry run pip install setuptools | |
| - name: Run tests | |
| run: poetry run python manage.py test | |
| env: | |
| DEBUG: True | |
| build: | |
| name: Build Image | |
| runs-on: ubuntu-latest | |
| needs: [ prepare, test ] | |
| outputs: | |
| image_tag: ${{ needs.prepare.outputs.image_tag }} | |
| deploy_ref: ${{ needs.prepare.outputs.deploy_ref }} | |
| steps: | |
| - name: "Checkout repository" | |
| uses: actions/checkout@v3 | |
| with: | |
| ref: ${{ needs.prepare.outputs.deploy_ref }} | |
| - name: "Set up QEMU" | |
| uses: docker/setup-qemu-action@v3 | |
| - name: "Set up Docker Buildx" | |
| uses: docker/setup-buildx-action@v3 | |
| - name: "Login to GitHub Registry" | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Docker meta | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ghcr.io/procollab-github/api | |
| tags: | | |
| type=raw,value=${{ needs.prepare.outputs.image_tag }} | |
| - name: Build and push container | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| file: Dockerfile | |
| push: true | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| deploy: | |
| name: Deploy | |
| runs-on: ubuntu-latest | |
| needs: [ prepare, build ] | |
| steps: | |
| - name: Deploy to server | |
| uses: garygrossgarten/github-action-ssh@release | |
| with: | |
| host: ${{ secrets.SERVER_HOST }} | |
| username: ${{ secrets.SERVER_USER }} | |
| password: ${{ secrets.SERVER_PASSWORD }} | |
| command: | | |
| set -eu | |
| 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 | |
| git -C /home/app/procollab-backend fetch --all --tags --prune | |
| git -C /home/app/procollab-backend checkout --detach "${DEPLOY_REF}" | |
| git -C /home/app/procollab-backend rev-parse HEAD | |
| else | |
| sudo -u app git -C /home/app/procollab-backend fetch --all --tags --prune | |
| sudo -u app git -C /home/app/procollab-backend checkout --detach "${DEPLOY_REF}" | |
| sudo -u app git -C /home/app/procollab-backend rev-parse HEAD | |
| fi | |
| cd /home/app/procollab-backend | |
| rm -f .env | |
| touch .env | |
| echo "DJANGO_SECRET_KEY=${{ secrets.DJANGO_SECRET_KEY }}" >> .env | |
| echo "DATABASE_NAME=${{ secrets.DATABASE_NAME }}" >> .env | |
| echo "DATABASE_PASSWORD=${{ secrets.DATABASE_PASSWORD }}" >> .env | |
| echo "DATABASE_USER=${{ secrets.DATABASE_USER }}" >> .env | |
| echo "DATABASE_HOST=${{ secrets.DATABASE_HOST }}" >> .env | |
| echo "DATABASE_PORT=${{ secrets.DATABASE_PORT }}" >> .env | |
| echo "SELECTEL_ACCOUNT_ID=${{ secrets.SELECTEL_ACCOUNT_ID }}" >> .env | |
| echo "SELECTEL_CONTAINER_NAME=${{ secrets.SELECTEL_CONTAINER_NAME }}" >> .env | |
| echo "SELECTEL_CONTAINER_PASSWORD=${{ secrets.SELECTEL_CONTAINER_PASSWORD }}" >> .env | |
| echo "SELECTEL_CONTAINER_USERNAME=${{ secrets.SELECTEL_CONTAINER_USERNAME }}" >> .env | |
| echo "EMAIL_USER=${{ secrets.EMAIL_USER }}" >> .env | |
| echo "UNISENDER_GO_API_KEY=${{ secrets.UNISENDER_GO_API_KEY }}" >> .env | |
| 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 web celerys | |
| 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 | |
| if [ "$(id -u)" -eq 0 ]; then | |
| nginx -t | |
| systemctl reload nginx | |
| else | |
| sudo nginx -t | |
| sudo systemctl reload nginx | |
| fi | |
| for attempt in $(seq 1 24); do | |
| 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}" | |
| 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 --timeout=10' 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 | |
| } |