From 73d10f364c0e0672c13159f69fe6e8f385ec3dcb Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Tue, 30 Dec 2025 02:13:34 +0100 Subject: [PATCH] feat: Add Gitea CI/CD pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete CI/CD workflow with: - Lint job: Ruff, mypy (backend), ESLint, TypeScript (frontend) - Test job: pytest with 90% coverage threshold, Jest tests - Build job: Docker image builds with layer caching - Deploy job: Placeholder for production deployment - Security job: Bandit scan via Ruff, npm audit Closes #15 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .gitea/workflows/ci.yaml | 355 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100644 .gitea/workflows/ci.yaml diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml new file mode 100644 index 0000000..06d445c --- /dev/null +++ b/.gitea/workflows/ci.yaml @@ -0,0 +1,355 @@ +# Syndarix CI/CD Pipeline +# Gitea Actions workflow for continuous integration and deployment +# +# Pipeline Structure: +# - lint: Fast feedback (linting and type checking) +# - test: Run test suites (depends on lint) +# - build: Build Docker images (depends on test) +# - deploy: Deploy to production (depends on build, only on main) + +name: CI/CD Pipeline + +on: + push: + branches: + - main + - dev + - 'feature/**' + pull_request: + branches: + - main + - dev + +env: + PYTHON_VERSION: "3.12" + NODE_VERSION: "20" + UV_VERSION: "0.4.x" + +jobs: + # =========================================================================== + # LINT JOB - Fast feedback first + # =========================================================================== + lint: + name: Lint & Type Check + runs-on: ubuntu-latest + strategy: + matrix: + component: [backend, frontend] + steps: + - name: Checkout code + uses: actions/checkout@v4 + + # ----- Backend Linting ----- + - name: Set up Python + if: matrix.component == 'backend' + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install uv + if: matrix.component == 'backend' + uses: astral-sh/setup-uv@v4 + with: + version: ${{ env.UV_VERSION }} + + - name: Cache uv dependencies + if: matrix.component == 'backend' + uses: actions/cache@v4 + with: + path: | + ~/.cache/uv + backend/.venv + key: uv-${{ runner.os }}-${{ hashFiles('backend/uv.lock') }} + restore-keys: | + uv-${{ runner.os }}- + + - name: Install backend dependencies + if: matrix.component == 'backend' + working-directory: backend + run: uv sync --extra dev --frozen + + - name: Run ruff linting + if: matrix.component == 'backend' + working-directory: backend + run: uv run ruff check app + + - name: Run ruff format check + if: matrix.component == 'backend' + working-directory: backend + run: uv run ruff format --check app + + - name: Run mypy type checking + if: matrix.component == 'backend' + working-directory: backend + run: uv run mypy app + + # ----- Frontend Linting ----- + - name: Set up Node.js + if: matrix.component == 'frontend' + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Cache npm dependencies + if: matrix.component == 'frontend' + uses: actions/cache@v4 + with: + path: | + ~/.npm + frontend/node_modules + key: npm-${{ runner.os }}-${{ hashFiles('frontend/package-lock.json') }} + restore-keys: | + npm-${{ runner.os }}- + + - name: Install frontend dependencies + if: matrix.component == 'frontend' + working-directory: frontend + run: npm ci + + - name: Run ESLint + if: matrix.component == 'frontend' + working-directory: frontend + run: npm run lint + + - name: Run TypeScript type check + if: matrix.component == 'frontend' + working-directory: frontend + run: npm run type-check + + - name: Run Prettier format check + if: matrix.component == 'frontend' + working-directory: frontend + run: npm run format:check + + # =========================================================================== + # TEST JOB - Run test suites + # =========================================================================== + test: + name: Test + runs-on: ubuntu-latest + needs: lint + strategy: + matrix: + component: [backend, frontend] + steps: + - name: Checkout code + uses: actions/checkout@v4 + + # ----- Backend Tests ----- + - name: Set up Python + if: matrix.component == 'backend' + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install uv + if: matrix.component == 'backend' + uses: astral-sh/setup-uv@v4 + with: + version: ${{ env.UV_VERSION }} + + - name: Cache uv dependencies + if: matrix.component == 'backend' + uses: actions/cache@v4 + with: + path: | + ~/.cache/uv + backend/.venv + key: uv-${{ runner.os }}-${{ hashFiles('backend/uv.lock') }} + restore-keys: | + uv-${{ runner.os }}- + + - name: Install backend dependencies + if: matrix.component == 'backend' + working-directory: backend + run: uv sync --extra dev --frozen + + - name: Run pytest with coverage + if: matrix.component == 'backend' + working-directory: backend + env: + IS_TEST: "True" + run: | + uv run pytest --cov=app --cov-report=xml --cov-report=term-missing --cov-fail-under=90 + + - name: Upload backend coverage report + if: matrix.component == 'backend' + uses: actions/upload-artifact@v4 + with: + name: backend-coverage + path: backend/coverage.xml + retention-days: 7 + + # ----- Frontend Tests ----- + - name: Set up Node.js + if: matrix.component == 'frontend' + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Cache npm dependencies + if: matrix.component == 'frontend' + uses: actions/cache@v4 + with: + path: | + ~/.npm + frontend/node_modules + key: npm-${{ runner.os }}-${{ hashFiles('frontend/package-lock.json') }} + restore-keys: | + npm-${{ runner.os }}- + + - name: Install frontend dependencies + if: matrix.component == 'frontend' + working-directory: frontend + run: npm ci + + - name: Run Jest unit tests + if: matrix.component == 'frontend' + working-directory: frontend + run: npm test -- --coverage --passWithNoTests + + - name: Upload frontend coverage report + if: matrix.component == 'frontend' + uses: actions/upload-artifact@v4 + with: + name: frontend-coverage + path: frontend/coverage/ + retention-days: 7 + + # =========================================================================== + # BUILD JOB - Build Docker images + # =========================================================================== + build: + name: Build + runs-on: ubuntu-latest + needs: test + strategy: + matrix: + component: [backend, frontend] + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Cache Docker layers + uses: actions/cache@v4 + with: + path: /tmp/.buildx-cache + key: docker-${{ matrix.component }}-${{ github.sha }} + restore-keys: | + docker-${{ matrix.component }}- + + - name: Build backend Docker image + if: matrix.component == 'backend' + uses: docker/build-push-action@v5 + with: + context: ./backend + file: ./backend/Dockerfile + target: production + push: false + tags: syndarix-backend:${{ github.sha }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max + + - name: Build frontend Docker image + if: matrix.component == 'frontend' + uses: docker/build-push-action@v5 + with: + context: ./frontend + file: ./frontend/Dockerfile + target: runner + push: false + tags: syndarix-frontend:${{ github.sha }} + build-args: | + NEXT_PUBLIC_API_URL=http://localhost:8000 + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max + + # Prevent cache from growing indefinitely + - name: Move cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache + + # =========================================================================== + # DEPLOY JOB - Deploy to production (only on main branch) + # =========================================================================== + deploy: + name: Deploy + runs-on: ubuntu-latest + needs: build + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + environment: production + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Deploy notification + run: | + echo "Deployment to production would happen here" + echo "Branch: ${{ github.ref }}" + echo "Commit: ${{ github.sha }}" + echo "Actor: ${{ github.actor }}" + + # TODO: Add actual deployment steps when infrastructure is ready + # Options: + # - SSH to production server and run docker-compose pull && docker-compose up -d + # - Use Kubernetes deployment + # - Use cloud provider deployment (AWS ECS, GCP Cloud Run, etc.) + # - Trigger webhook to deployment orchestrator + + # =========================================================================== + # SECURITY SCAN JOB - Run on main and dev branches + # =========================================================================== + security: + name: Security Scan + runs-on: ubuntu-latest + needs: lint + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: ${{ env.UV_VERSION }} + + - name: Install backend dependencies + working-directory: backend + run: uv sync --extra dev --frozen + + - name: Run Bandit security scan (via ruff) + working-directory: backend + run: | + # Ruff includes flake8-bandit (S rules) for security scanning + # Run with explicit security rules only + uv run ruff check app --select=S --ignore=S101,S104,S105,S106,S603,S607 + + - name: Check for secrets in code + run: | + # Basic check for common secret patterns + # In production, use tools like gitleaks or trufflehog + echo "Checking for potential hardcoded secrets..." + ! grep -rn --include="*.py" --include="*.ts" --include="*.tsx" --include="*.js" \ + -E "(api_key|apikey|secret_key|secretkey|password|passwd|token)\s*=\s*['\"][^'\"]{8,}['\"]" \ + backend/app frontend/src || echo "No obvious secrets found" + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Run npm audit + working-directory: frontend + run: | + npm audit --audit-level=high || true + # Note: Using || true to not fail on moderate vulnerabilities + # In production, consider stricter settings