name: CI/CD Pipeline on: push: branches: [ main, develop ] pull_request: branches: [ main ] release: types: [ published ] env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: test: name: Run Tests runs-on: ubuntu-latest strategy: matrix: python-version: ['3.9', '3.10', '3.11'] steps: - name: Checkout code uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Cache Python dependencies uses: actions/cache@v5 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install pytest pytest-asyncio httpx - name: Lint with flake8 run: | pip install flake8 # Stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # Treat all errors as warnings flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test application startup run: | # Start server in background with CI mode (skips indexing) timeout 30 python run.py --host 127.0.0.1 --port 8001 --skip-index & SERVER_PID=$! # Wait for server to be ready (max 20 seconds) echo "Waiting for server to start..." for i in {1..20}; do if curl -f http://127.0.0.1:8001/api/stats 2>/dev/null; then echo "Server is ready!" break fi if [ $i -eq 20 ]; then echo "Server failed to start within 20 seconds" exit 1 fi echo "Attempt $i/20..." sleep 1 done # Clean up kill $SERVER_PID 2>/dev/null || true - name: Test Docker build run: | docker build -t test-image:latest . security: name: Security Scan runs-on: ubuntu-latest needs: test # Don't fail the workflow if Trivy finds issues continue-on-error: true permissions: contents: read security-events: write actions: read steps: - name: Checkout code uses: actions/checkout@v6 - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@master with: scan-type: 'fs' scan-ref: '.' format: 'sarif' output: 'trivy-results.sarif' severity: 'CRITICAL,HIGH' ignore-unfixed: true trivyignores: '.trivyignore' trivy-config: 'trivy.yaml' exit-code: '0' # Report only mode - won't fail the build vuln-type: 'os,library' skip-dirs: 'workflows,database,workflows_backup*,__pycache__,venv,.venv' - name: Upload Trivy scan results uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: 'trivy-results.sarif' build: name: Build and Push Docker Image runs-on: ubuntu-latest needs: [test, security] if: github.event_name != 'pull_request' permissions: contents: read packages: write id-token: write steps: - name: Checkout code uses: actions/checkout@v6 - 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=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=sha,prefix=sha- - name: Build and push Docker image uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max deploy-staging: name: Deploy to Staging runs-on: ubuntu-latest needs: build if: github.ref == 'refs/heads/develop' environment: staging steps: - name: Checkout code uses: actions/checkout@v6 - name: Deploy to staging run: | echo "Deploying to staging environment..." # Add your staging deployment commands here # Example: kubectl, docker-compose, or cloud provider CLI commands deploy-production: name: Deploy to Production runs-on: ubuntu-latest needs: build if: github.ref == 'refs/heads/main' || github.event_name == 'release' environment: production steps: - name: Checkout code uses: actions/checkout@v6 - name: Deploy to production run: | echo "Deploying to production environment..." # Add your production deployment commands here # Example: kubectl, docker-compose, or cloud provider CLI commands notification: name: Send Notifications runs-on: ubuntu-latest needs: [deploy-staging, deploy-production] if: always() && (needs.deploy-staging.result != 'skipped' || needs.deploy-production.result != 'skipped') steps: - name: Notify deployment status run: | if [[ "${{ needs.deploy-staging.result }}" == "success" || "${{ needs.deploy-production.result }}" == "success" ]]; then echo "Deployment successful!" else echo "Deployment failed!" fi