Some checks failed
Basil CI/CD Pipeline / Shared Package Tests (pull_request) Successful in 1m22s
Basil CI/CD Pipeline / Code Linting (pull_request) Successful in 1m25s
Basil CI/CD Pipeline / API Tests (pull_request) Failing after 1m25s
Basil CI/CD Pipeline / Web Tests (pull_request) Successful in 1m26s
Basil CI/CD Pipeline / Security Scanning (pull_request) Successful in 1m30s
Basil CI/CD Pipeline / Build All Packages (pull_request) Has been skipped
Basil CI/CD Pipeline / E2E Tests (pull_request) Has been skipped
Basil CI/CD Pipeline / Build & Push Docker Images (pull_request) Has been skipped
Basil CI/CD Pipeline / Trigger Deployment (pull_request) Has been skipped
- Add requirements.txt for Python dependencies - Use actions/setup-python@v6 with pip caching - Follow recommended GitHub Actions pattern for Python setup Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
442 lines
14 KiB
YAML
442 lines
14 KiB
YAML
name: Basil CI/CD Pipeline
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
- develop
|
|
tags:
|
|
- 'v*'
|
|
pull_request:
|
|
branches:
|
|
- main
|
|
- develop
|
|
|
|
env:
|
|
NODE_VERSION: '20'
|
|
HARBOR_REGISTRY: harbor.pkartchner.com
|
|
HARBOR_PROJECT: basil
|
|
|
|
jobs:
|
|
# ============================================================================
|
|
# STAGE 1: PARALLEL QUALITY CHECKS
|
|
# ============================================================================
|
|
|
|
lint:
|
|
name: Code Linting
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Run linter
|
|
run: npm run lint || true
|
|
continue-on-error: true
|
|
|
|
test-api:
|
|
name: API Tests
|
|
runs-on: ubuntu-latest
|
|
services:
|
|
postgres:
|
|
image: postgres:16-alpine
|
|
env:
|
|
POSTGRES_USER: basil
|
|
POSTGRES_PASSWORD: basil
|
|
POSTGRES_DB: basil_test
|
|
options: >-
|
|
--health-cmd pg_isready
|
|
--health-interval 10s
|
|
--health-timeout 5s
|
|
--health-retries 5
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v6
|
|
with:
|
|
python-version: '3.11'
|
|
cache: 'pip'
|
|
|
|
- name: Install Python dependencies
|
|
run: |
|
|
pip install -r packages/api/requirements.txt
|
|
python -c "import recipe_scrapers; print('✓ recipe-scrapers installed successfully')"
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Build shared package
|
|
run: cd packages/shared && npm run build
|
|
|
|
- name: Generate Prisma Client
|
|
run: cd packages/api && npm run prisma:generate
|
|
|
|
- name: Run database migrations
|
|
run: cd packages/api && npm run prisma:migrate
|
|
env:
|
|
DATABASE_URL: postgresql://basil:basil@postgres:5432/basil_test?schema=public
|
|
|
|
- name: Run API tests
|
|
run: cd packages/api && npm run test
|
|
env:
|
|
DATABASE_URL: postgresql://basil:basil@postgres:5432/basil_test?schema=public
|
|
NODE_ENV: test
|
|
|
|
- name: Upload coverage
|
|
if: always()
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: api-coverage
|
|
path: packages/api/coverage/
|
|
retention-days: 14
|
|
|
|
test-web:
|
|
name: Web Tests
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Build shared package
|
|
run: cd packages/shared && npm run build
|
|
|
|
- name: Run Web tests
|
|
run: cd packages/web && npm run test
|
|
|
|
- name: Upload coverage
|
|
if: always()
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: web-coverage
|
|
path: packages/web/coverage/
|
|
retention-days: 14
|
|
|
|
test-shared:
|
|
name: Shared Package Tests
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Run Shared tests
|
|
run: cd packages/shared && npm run test
|
|
|
|
- name: Upload coverage
|
|
if: always()
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: shared-coverage
|
|
path: packages/shared/coverage/
|
|
retention-days: 14
|
|
|
|
security-scan:
|
|
name: Security Scanning
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: NPM Audit
|
|
run: |
|
|
echo "Running npm audit..."
|
|
npm audit --audit-level=high || true
|
|
cd packages/api && npm audit --audit-level=high || true
|
|
cd ../web && npm audit --audit-level=high || true
|
|
continue-on-error: true
|
|
|
|
- name: Secret Scanning
|
|
run: |
|
|
echo "Scanning for hardcoded secrets..."
|
|
if grep -r -E "(password|secret|api[_-]?key|token)\s*=\s*['\"][^'\"]+['\"]" \
|
|
--include="*.ts" --include="*.js" \
|
|
--exclude-dir=node_modules --exclude-dir=dist --exclude-dir=e2e \
|
|
--exclude="*.test.ts" --exclude="*.spec.ts" .; then
|
|
echo "⚠️ Potential hardcoded secrets found in non-test files!"
|
|
exit 1
|
|
fi
|
|
echo "✓ No hardcoded secrets detected"
|
|
|
|
- name: Check outdated dependencies
|
|
run: |
|
|
echo "Checking for outdated dependencies..."
|
|
npm outdated || true
|
|
continue-on-error: true
|
|
|
|
# ============================================================================
|
|
# STAGE 2: BUILD VERIFICATION
|
|
# ============================================================================
|
|
|
|
build:
|
|
name: Build All Packages
|
|
runs-on: ubuntu-latest
|
|
needs: [lint, test-api, test-web, test-shared, security-scan]
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Build all packages
|
|
run: npm run build
|
|
|
|
- name: Upload build artifacts
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: build-artifacts
|
|
path: |
|
|
packages/api/dist/
|
|
packages/web/dist/
|
|
packages/shared/dist/
|
|
retention-days: 7
|
|
|
|
# ============================================================================
|
|
# STAGE 3: E2E TESTING
|
|
# ============================================================================
|
|
|
|
e2e-tests:
|
|
name: E2E Tests
|
|
runs-on: ubuntu-latest
|
|
needs: build
|
|
timeout-minutes: 30
|
|
if: false # Temporarily disabled - E2E tests need fixing
|
|
services:
|
|
postgres:
|
|
image: postgres:16-alpine
|
|
env:
|
|
POSTGRES_USER: basil
|
|
POSTGRES_PASSWORD: basil
|
|
POSTGRES_DB: basil
|
|
options: >-
|
|
--health-cmd pg_isready
|
|
--health-interval 10s
|
|
--health-timeout 5s
|
|
--health-retries 5
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Install Playwright browsers
|
|
run: npx playwright install --with-deps
|
|
|
|
- name: Build application
|
|
run: npm run build
|
|
|
|
- name: Run database migrations
|
|
run: cd packages/api && npm run prisma:migrate
|
|
env:
|
|
DATABASE_URL: postgresql://basil:basil@postgres:5432/basil?schema=public
|
|
|
|
- name: Run E2E tests
|
|
run: npm run test:e2e
|
|
env:
|
|
DATABASE_URL: postgresql://basil:basil@postgres:5432/basil?schema=public
|
|
|
|
- name: Upload test results
|
|
if: always()
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: playwright-report
|
|
path: playwright-report/
|
|
retention-days: 14
|
|
|
|
# ============================================================================
|
|
# STAGE 4: DOCKER BUILD & DEPLOYMENT (main branch only)
|
|
# ============================================================================
|
|
|
|
docker-build-and-push:
|
|
name: Build & Push Docker Images
|
|
runs-on: ubuntu-latest
|
|
needs: build # Changed from e2e-tests since E2E is temporarily disabled
|
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
outputs:
|
|
image_tag: ${{ steps.meta.outputs.tag }}
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install Docker CLI
|
|
run: |
|
|
mkdir -p /tmp/docker-cli
|
|
curl -fsSL "https://download.docker.com/linux/static/stable/x86_64/docker-27.4.1.tgz" | tar -xz -C /tmp/docker-cli --strip-components=1
|
|
export PATH="/tmp/docker-cli:$PATH"
|
|
echo "/tmp/docker-cli" >> $GITHUB_PATH
|
|
docker --version
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@v3
|
|
|
|
- name: Log in to Harbor
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ${{ env.HARBOR_REGISTRY }}
|
|
username: ${{ secrets.HARBOR_USERNAME }}
|
|
password: ${{ secrets.HARBOR_PASSWORD }}
|
|
|
|
- name: Extract metadata
|
|
id: meta
|
|
run: |
|
|
SHA_SHORT=$(echo $GITHUB_SHA | cut -c1-7)
|
|
echo "tag=main-${SHA_SHORT}" >> $GITHUB_OUTPUT
|
|
echo "date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
|
|
|
|
- name: Build and push API image
|
|
uses: docker/build-push-action@v5
|
|
with:
|
|
context: .
|
|
file: ./packages/api/Dockerfile
|
|
push: true
|
|
tags: |
|
|
${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_PROJECT }}/basil-api:latest
|
|
${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_PROJECT }}/basil-api:${{ steps.meta.outputs.tag }}
|
|
labels: |
|
|
org.opencontainers.image.created=${{ steps.meta.outputs.date }}
|
|
org.opencontainers.image.revision=${{ github.sha }}
|
|
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
|
|
|
|
- name: Build and push Web image
|
|
uses: docker/build-push-action@v5
|
|
with:
|
|
context: .
|
|
file: ./packages/web/Dockerfile
|
|
push: true
|
|
tags: |
|
|
${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_PROJECT }}/basil-web:latest
|
|
${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_PROJECT }}/basil-web:${{ steps.meta.outputs.tag }}
|
|
labels: |
|
|
org.opencontainers.image.created=${{ steps.meta.outputs.date }}
|
|
org.opencontainers.image.revision=${{ github.sha }}
|
|
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
|
|
|
|
- name: Scan API image for vulnerabilities
|
|
run: |
|
|
docker run --rm \
|
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
aquasec/trivy:latest image \
|
|
--exit-code 0 \
|
|
--severity HIGH,CRITICAL \
|
|
--no-progress \
|
|
${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_PROJECT }}/basil-api:latest || true
|
|
|
|
- name: Scan Web image for vulnerabilities
|
|
run: |
|
|
docker run --rm \
|
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
aquasec/trivy:latest image \
|
|
--exit-code 0 \
|
|
--severity HIGH,CRITICAL \
|
|
--no-progress \
|
|
${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_PROJECT }}/basil-web:latest || true
|
|
|
|
- name: Image build summary
|
|
run: |
|
|
echo "### Docker Images Built & Pushed 🐳" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "**API Image:**" >> $GITHUB_STEP_SUMMARY
|
|
echo "- \`${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_PROJECT }}/basil-api:latest\`" >> $GITHUB_STEP_SUMMARY
|
|
echo "- \`${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_PROJECT }}/basil-api:${{ steps.meta.outputs.tag }}\`" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "**Web Image:**" >> $GITHUB_STEP_SUMMARY
|
|
echo "- \`${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_PROJECT }}/basil-web:latest\`" >> $GITHUB_STEP_SUMMARY
|
|
echo "- \`${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_PROJECT }}/basil-web:${{ steps.meta.outputs.tag }}\`" >> $GITHUB_STEP_SUMMARY
|
|
|
|
trigger-deployment:
|
|
name: Trigger Deployment
|
|
runs-on: ubuntu-latest
|
|
needs: docker-build-and-push
|
|
if: success()
|
|
steps:
|
|
- name: Trigger webhook
|
|
run: |
|
|
# Install required packages
|
|
apt-get update -qq && apt-get install -y -qq jq iproute2 > /dev/null 2>&1
|
|
|
|
# Get Docker host IP (gateway of the container network)
|
|
HOST_IP=$(ip route | grep default | awk '{print $3}')
|
|
|
|
# Fallback to common Docker gateway IPs if not found
|
|
if [ -z "$HOST_IP" ]; then
|
|
HOST_IP="172.17.0.1"
|
|
fi
|
|
|
|
echo "Using host IP: $HOST_IP"
|
|
|
|
# Construct JSON payload with jq to properly escape multiline commit messages
|
|
PAYLOAD=$(jq -n \
|
|
--arg branch "main" \
|
|
--arg commit "${{ github.sha }}" \
|
|
--arg message "${{ github.event.head_commit.message }}" \
|
|
--arg tag "${{ needs.docker-build-and-push.outputs.image_tag }}" \
|
|
'{branch: $branch, commit: $commit, message: $message, tag: $tag}')
|
|
|
|
# Trigger webhook on Docker host
|
|
curl -X POST "http://${HOST_IP}:9000/hooks/basil-deploy" \
|
|
-H "Content-Type: application/json" \
|
|
-H "X-Webhook-Secret: ${{ secrets.WEBHOOK_SECRET }}" \
|
|
-d "$PAYLOAD" || echo "Webhook call failed, but continuing..."
|
|
|
|
- name: Deployment triggered
|
|
run: |
|
|
echo "### Deployment Triggered 🚀" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "The deployment webhook has been called." >> $GITHUB_STEP_SUMMARY
|
|
echo "Check the server logs to verify deployment status:" >> $GITHUB_STEP_SUMMARY
|
|
echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
|
|
echo "tail -f /srv/docker-compose/basil/deploy.log" >> $GITHUB_STEP_SUMMARY
|
|
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "**Application URL:** https://basil.pkartchner.com" >> $GITHUB_STEP_SUMMARY
|