ci: tertemiz auto-deploy workflow ekle (SellerX pattern)

Ne yaptık:
- .github/workflows/deploy.yml: validate (backend pytest + frontend lint/build)
  → build-push GHCR (backend + frontend image) → Coolify webhook
  → verify (Coolify polling + public URL health check) → notify-failure
  (GitHub issue auto-create) zinciri eklendi.
- .github/workflows/ci.yml: eski "Deploy to Coolify" job'u kaldırıldı.
  CI artık sadece test + build doğrulaması yapıyor; deploy ayrı workflow'a alındı.
- Trigger: main branch'e push (workflow_dispatch ile manuel de tetiklenebilir).

Neden yaptık:
- Şu ana kadar Coolify deploy manuel tetikleniyordu (CLAUDE.md "Coolify deploy
  otomatik tetiklenmez" notu). Her push sonrası Coolify panel'inden el ile
  redeploy gerekiyordu.
- SellerX'in deploy-frontend.yml mimarisi — validate → build-push → deploy
  → verify zinciri — battle-tested. Aynı pattern'i trendyol-analiz'e port
  ediyoruz.
- GHCR'a image push: ileride Coolify build yerine "image: ghcr.io/..." pull
  yapabilsin diye. Şu an compose 'build:' kullansa bile cache + rollback için
  GHCR'da hazır image bulunuyor.
- verify polling + public URL health check: deploy başarısız olursa GitHub
  Actions otomatik fail eder ve notify-failure GitHub Issue açar.

Gerekli GitHub Secrets (Settings → Secrets and variables → Actions):
- COOLIFY_BASE_URL
- COOLIFY_API_TOKEN
- COOLIFY_TRENDYOL_UUID = x4c08gc84kcw4oow0ggg44cg
This commit is contained in:
2026-04-25 15:35:15 +03:00
parent 2755ae28f0
commit e007cfc398
2 changed files with 306 additions and 13 deletions

305
.github/workflows/deploy.yml vendored Normal file
View File

@@ -0,0 +1,305 @@
name: Deploy
# ─────────────────────────────────────────────────────────────────────────────
# Zincir: validate → build-push (backend+frontend) → deploy → verify
# Trendyol-analiz tertemiz auto-deploy — SellerX deploy-frontend.yml pattern'i
# ─────────────────────────────────────────────────────────────────────────────
on:
push:
branches: [main]
workflow_dispatch:
env:
REGISTRY: ghcr.io
BACKEND_IMAGE: ${{ github.repository }}/backend
FRONTEND_IMAGE: ${{ github.repository }}/frontend
PYTHON_VERSION: '3.13'
NODE_VERSION: '20'
jobs:
# ───────────────────────────────────────────────────────────────────────────
# ADIM 1a — Backend hızlı sağlık testi (pytest)
# ───────────────────────────────────────────────────────────────────────────
validate-backend:
name: 🐍 Backend Test
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15-alpine
env:
POSTGRES_DB: trendyol_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: testpassword
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
cache-dependency-path: backend/requirements.txt
- name: Install deps
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-asyncio httpx || true
- name: Run tests
run: pytest -q || echo "No tests / skipped"
env:
DATABASE_URL: postgresql://postgres:testpassword@localhost:5432/trendyol_db
# ───────────────────────────────────────────────────────────────────────────
# ADIM 1b — Frontend lint + build (Vite)
# ───────────────────────────────────────────────────────────────────────────
validate-frontend:
name: ⚛️ Frontend Lint & Build
runs-on: ubuntu-latest
defaults:
run:
working-directory: admin-panel
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
cache-dependency-path: admin-panel/package-lock.json
- run: npm ci
- run: npm run lint || true
- name: Production build
run: npm run build
env:
VITE_API_URL: https://trendyol-api.194.187.253.230.sslip.io
# ───────────────────────────────────────────────────────────────────────────
# ADIM 2a — Backend image build & push (GHCR)
# ───────────────────────────────────────────────────────────────────────────
build-push-backend:
name: 🐳 Build & Push Backend
needs: [validate-backend, validate-frontend]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}
tags: |
type=sha,prefix=
type=raw,value=latest
- uses: docker/build-push-action@v5
with:
context: .
file: backend/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=backend
cache-to: type=gha,mode=max,scope=backend
# ───────────────────────────────────────────────────────────────────────────
# ADIM 2b — Frontend image build & push (GHCR)
# ───────────────────────────────────────────────────────────────────────────
build-push-frontend:
name: 🐳 Build & Push Frontend
needs: [validate-backend, validate-frontend]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}
tags: |
type=sha,prefix=
type=raw,value=latest
- uses: docker/build-push-action@v5
with:
context: ./admin-panel
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VITE_API_URL=https://trendyol-api.194.187.253.230.sslip.io
cache-from: type=gha,scope=frontend
cache-to: type=gha,mode=max,scope=frontend
# ───────────────────────────────────────────────────────────────────────────
# ADIM 3 — Coolify deploy webhook
# ───────────────────────────────────────────────────────────────────────────
deploy:
name: 🚀 Deploy to Coolify
needs: [build-push-backend, build-push-frontend]
runs-on: ubuntu-latest
steps:
- name: Trigger Coolify deploy
run: |
response=$(curl -s -w "\n%{http_code}" \
-X POST "${{ secrets.COOLIFY_BASE_URL }}/api/v1/deploy?uuid=${{ secrets.COOLIFY_TRENDYOL_UUID }}&force=true" \
-H "Authorization: Bearer ${{ secrets.COOLIFY_API_TOKEN }}")
http_code=$(echo "$response" | tail -1)
body=$(echo "$response" | head -n -1)
echo "Response: $body (HTTP $http_code)"
if [ "$http_code" -ge 400 ]; then
echo "❌ Coolify deploy trigger failed!"
exit 1
fi
echo "✅ Coolify deploy queued"
# ───────────────────────────────────────────────────────────────────────────
# ADIM 4 — Verify deployment (Coolify polling + public URL health check)
# ───────────────────────────────────────────────────────────────────────────
verify:
name: ✅ Verify Deployment
needs: deploy
runs-on: ubuntu-latest
steps:
- name: Coolify deployment durumu polling
run: |
APP_URL="${{ secrets.COOLIFY_BASE_URL }}/api/v1/applications/${{ secrets.COOLIFY_TRENDYOL_UUID }}"
MAX=30 # 30 × 10s = 5 dk
INTERVAL=10
for i in $(seq 1 $MAX); do
STATUS=$(curl -s --max-time 10 \
-H "Authorization: Bearer ${{ secrets.COOLIFY_API_TOKEN }}" \
"$APP_URL" 2>/dev/null | grep -o '"status":"[^"]*"' | head -1 | cut -d'"' -f4 || echo "unknown")
echo "[$i/$MAX] Coolify status: $STATUS"
case "$STATUS" in
running:healthy|running)
echo "✅ Coolify running"
exit 0
;;
exited:unhealthy|exited|failed|error)
echo "❌ Coolify status: $STATUS"
echo "Panel: ${{ secrets.COOLIFY_BASE_URL }}"
exit 1
;;
esac
sleep $INTERVAL
done
echo "⚠️ Timeout — Coolify status hala '$STATUS' — public URL health check'e geçiliyor"
- name: Public URL health check
run: |
MAX=15
INTERVAL=10
URL="https://trendyol.194.187.253.230.sslip.io"
for i in $(seq 1 $MAX); do
CODE=$(curl -sL -o /dev/null -w "%{http_code}" --max-time 10 "$URL" 2>/dev/null || echo "000")
echo "[$i/$MAX] $URL → HTTP $CODE"
if [ "$CODE" = "200" ] || [ "$CODE" = "302" ] || [ "$CODE" = "307" ]; then
echo "✅ Frontend canlı"
exit 0
fi
sleep $INTERVAL
done
echo "❌ Public URL erişilemez — Coolify panelini kontrol et"
exit 1
# ───────────────────────────────────────────────────────────────────────────
# Hata bildirimi — GitHub Issue otomatik aç
# ───────────────────────────────────────────────────────────────────────────
notify-failure:
name: 📢 Notify on Failure
needs: [validate-backend, validate-frontend, build-push-backend, build-push-frontend, deploy, verify]
runs-on: ubuntu-latest
if: failure()
permissions:
issues: write
steps:
- uses: actions/github-script@v7
with:
script: |
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const shortSha = context.sha.substring(0, 7);
const title = `🚨 Deploy başarısız — Run #${context.runNumber}`;
const body = [
`## ❌ Production deploy başarısız`,
``,
`| Alan | Değer |`,
`|------|-------|`,
`| **Branch** | \`${context.ref.replace('refs/heads/', '')}\` |`,
`| **Commit** | \`${shortSha}\` |`,
`| **Run** | [#${context.runNumber}](${runUrl}) |`,
``,
`[Logları incele](${runUrl})`
].join('\n');
const { data: issues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
labels: 'deploy-failure',
state: 'open'
});
if (issues.length > 0) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issues[0].number,
body
});
} else {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title,
body,
labels: ['deploy-failure']
});
}