name: CI/CD Pipeline on: push: branches: [ main, develop ] tags: [ 'v*' ] pull_request: branches: [ main, develop ] env: POSTGRES_DB: platform_test POSTGRES_USER: platform_user POSTGRES_PASSWORD: test_password DATABASE_URL: "postgresql://platform_user:test_password@localhost:5432/platform_test" REDIS_URL: "redis://localhost:6379/0" jobs: # Backend тесты test-backend: runs-on: ubuntu-latest services: postgres: image: postgres:16-alpine env: POSTGRES_DB: ${{ env.POSTGRES_DB }} POSTGRES_USER: ${{ env.POSTGRES_USER }} POSTGRES_PASSWORD: ${{ env.POSTGRES_PASSWORD }} ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 redis: image: redis:7-alpine ports: - 6379:6379 options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python 3.11 uses: actions/setup-python@v5 with: python-version: '3.11' cache: 'pip' - name: Install dependencies working-directory: ./backend run: | python -m pip install --upgrade pip pip install -r requirements.txt - name: Run Django checks working-directory: ./backend env: DJANGO_SETTINGS_MODULE: config.settings SECRET_KEY: test-secret-key-for-ci DEBUG: False ALLOWED_HOSTS: "*" run: python manage.py check - name: Run tests with coverage working-directory: ./backend env: DJANGO_SETTINGS_MODULE: config.settings SECRET_KEY: test-secret-key-for-ci DEBUG: False ALLOWED_HOSTS: "*" run: | pytest --cov=apps --cov-report=xml --cov-report=html --cov-report=term-missing - name: Upload coverage reports uses: codecov/codecov-action@v3 with: file: ./backend/coverage.xml flags: backend name: backend-coverage # Backend linting lint-backend: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python 3.11 uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install linting tools run: pip install flake8 black isort - name: Run flake8 working-directory: ./backend run: flake8 apps config --max-line-length=120 --exclude=migrations,__pycache__ - name: Run black working-directory: ./backend run: black --check apps config --exclude migrations - name: Run isort working-directory: ./backend run: isort --check-only apps config --skip migrations # Frontend тесты test-frontend: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Node.js 18 uses: actions/setup-node@v4 with: node-version: '18' cache: 'npm' cache-dependency-path: frontend/package-lock.json - name: Install dependencies working-directory: ./frontend run: npm ci - name: Run linting working-directory: ./frontend run: npm run lint - name: Run type checking working-directory: ./frontend run: npm run type-check || true - name: Build working-directory: ./frontend run: npm run build # Security проверки security-backend: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python 3.11 uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install security tools run: pip install safety bandit - name: Run safety check working-directory: ./backend run: safety check --json || true - name: Run bandit working-directory: ./backend run: bandit -r apps config -ll || true # Сборка Docker образов build: needs: [test-backend, test-frontend, lint-backend] runs-on: ubuntu-latest if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/tags/v')) steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: | ${{ secrets.DOCKER_USERNAME }}/platform-backend ${{ secrets.DOCKER_USERNAME }}/platform-frontend - name: Build and push Backend image uses: docker/build-push-action@v5 with: context: ./backend push: true tags: ${{ secrets.DOCKER_USERNAME }}/platform-backend:${{ github.ref_name }} cache-from: type=gha cache-to: type=gha,mode=max - name: Build and push Frontend image uses: docker/build-push-action@v5 with: context: ./frontend push: true tags: ${{ secrets.DOCKER_USERNAME }}/platform-frontend:${{ github.ref_name }} cache-from: type=gha cache-to: type=gha,mode=max # Deploy на Staging deploy-staging: needs: [build] runs-on: ubuntu-latest if: github.ref == 'refs/heads/develop' environment: name: staging url: https://staging.platform.example.com steps: - name: Deploy to Staging uses: appleboy/ssh-action@v1.0.0 with: host: ${{ secrets.STAGING_HOST }} username: ${{ secrets.STAGING_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} script: | cd /opt/platform docker compose pull docker compose up -d # Deploy на Production deploy-production: needs: [build] runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') environment: name: production url: https://platform.example.com steps: - name: Deploy to Production uses: appleboy/ssh-action@v1.0.0 with: host: ${{ secrets.PRODUCTION_HOST }} username: ${{ secrets.PRODUCTION_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} script: | cd /opt/platform docker compose pull docker compose up -d - name: Notify deployment if: always() run: echo "Deployment completed with status ${{ job.status }}"