name: Continuous Integration on: push: branches: [ main, develop, 'feature/*', 'feat/*', 'hotfix/*' ] pull_request: branches: [ main, develop ] workflow_dispatch: env: PYTHON_VERSION: '3.11' NODE_VERSION: '18' REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: # Code Quality and Security Checks code-quality: name: Code Quality & Security runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install black flake8 mypy bandit safety - name: Code formatting check (Black) run: black --check --diff src/ tests/ - name: Linting (Flake8) run: flake8 src/ tests/ --max-line-length=88 --extend-ignore=E203,W503 - name: Type checking (MyPy) run: mypy src/ --ignore-missing-imports - name: Security scan (Bandit) run: bandit -r src/ -f json -o bandit-report.json continue-on-error: true - name: Dependency vulnerability scan (Safety) run: safety check --json --output safety-report.json continue-on-error: true - name: Upload security reports uses: actions/upload-artifact@v4 if: always() with: name: security-reports path: | bandit-report.json safety-report.json # Unit and Integration Tests test: name: Tests runs-on: ubuntu-latest strategy: matrix: python-version: ['3.10', '3.11', '3.12'] services: postgres: image: postgres:15 env: POSTGRES_PASSWORD: postgres POSTGRES_DB: test_wifi_densepose options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 5432:5432 redis: image: redis:7 options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 6379:6379 steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install pytest-cov pytest-xdist - name: Run unit tests env: DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_wifi_densepose REDIS_URL: redis://localhost:6379/0 ENVIRONMENT: test run: | pytest tests/unit/ -v --cov=src --cov-report=xml --cov-report=html --junitxml=junit.xml - name: Run integration tests env: DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_wifi_densepose REDIS_URL: redis://localhost:6379/0 ENVIRONMENT: test run: | pytest tests/integration/ -v --junitxml=integration-junit.xml - name: Upload coverage reports uses: codecov/codecov-action@v4 with: file: ./coverage.xml flags: unittests name: codecov-umbrella - name: Upload test results uses: actions/upload-artifact@v4 if: always() with: name: test-results-${{ matrix.python-version }} path: | junit.xml integration-junit.xml htmlcov/ # Performance and Load Tests performance-test: name: Performance Tests runs-on: ubuntu-latest needs: [test] if: github.event_name == 'push' && github.ref == 'refs/heads/main' steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install locust - name: Start application run: | uvicorn src.api.main:app --host 0.0.0.0 --port 8000 & sleep 10 - name: Run performance tests run: | locust -f tests/performance/locustfile.py --headless --users 50 --spawn-rate 5 --run-time 60s --host http://localhost:8000 - name: Upload performance results uses: actions/upload-artifact@v4 with: name: performance-results path: locust_report.html # Docker Build and Test docker-build: name: Docker Build & Test runs-on: ubuntu-latest needs: [code-quality, test] steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=ref,event=branch type=ref,event=pr type=sha,prefix={{branch}}- type=raw,value=latest,enable={{is_default_branch}} - name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . target: production push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max platforms: linux/amd64,linux/arm64 - name: Test Docker image run: | docker run --rm -d --name test-container -p 8000:8000 ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} sleep 10 curl -f http://localhost:8000/health || exit 1 docker stop test-container - name: Run container security scan uses: aquasecurity/trivy-action@master with: image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} format: 'sarif' output: 'trivy-results.sarif' - name: Upload Trivy scan results uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: 'trivy-results.sarif' # API Documentation docs: name: API Documentation runs-on: ubuntu-latest needs: [docker-build] if: github.ref == 'refs/heads/main' steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt - name: Generate OpenAPI spec run: | python -c " from src.api.main import app import json with open('openapi.json', 'w') as f: json.dump(app.openapi(), f, indent=2) " - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./docs destination_dir: api-docs # Notification notify: name: Notify runs-on: ubuntu-latest needs: [code-quality, test, performance-test, docker-build, docs] if: always() steps: - name: Notify Slack on success if: ${{ secrets.SLACK_WEBHOOK_URL != '' && needs.code-quality.result == 'success' && needs.test.result == 'success' && needs.docker-build.result == 'success' }} uses: 8398a7/action-slack@v3 with: status: success channel: '#ci-cd' text: '✅ CI pipeline completed successfully for ${{ github.ref }}' env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - name: Notify Slack on failure if: ${{ secrets.SLACK_WEBHOOK_URL != '' && (needs.code-quality.result == 'failure' || needs.test.result == 'failure' || needs.docker-build.result == 'failure') }} uses: 8398a7/action-slack@v3 with: status: failure channel: '#ci-cd' text: '❌ CI pipeline failed for ${{ github.ref }}' env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - name: Create GitHub Release if: github.ref == 'refs/heads/main' && needs.docker-build.result == 'success' uses: softprops/action-gh-release@v2 with: tag_name: v${{ github.run_number }} name: Release v${{ github.run_number }} body: | Automated release from CI pipeline **Changes:** ${{ github.event.head_commit.message }} **Docker Image:** `${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}` draft: false prerelease: false