diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 54756ec..5832501 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -81,8 +81,10 @@ See `.env.sample` for the full list. Key ones: ## Git Workflow -- CI: Woodpecker CI (`.woodpecker/`) builds Docker images on push to `master`, deploys to K8s. -- Linting: GitHub Actions runs Ruff on PR diffs. +- **CI: GitHub Actions** (`.github/workflows/build-api.yml`, `build-frontend.yml`) builds + pushes Docker images to DockerHub on `master` push. **Keel** in the cluster watches `:latest` on `viktorbarzin/realestatecrawler` and `viktorbarzin/immoweb` and rolls the four `realestate-crawler-*` Deployments on digest change. No Woodpecker deploy POST — Keel is the rollout mechanism. +- Pull-secret on the namespace: `dockerhub-pull-secret`, synced from Vault `secret/viktor.dockerhub_registry_password` via ExternalSecret (codified in `infra/stacks/real-estate-crawler/main.tf`). Required because the DockerHub repos are private. +- Fallback: `.woodpecker/build-fallback-{api,frontend}.yml` (event: `deployment`, manual-only) preserves the in-cluster build path if GHA is down. +- Linting: GitHub Actions runs Ruff on PR diffs (`.github/workflows/ruff.yml`). - Keep commits focused — one logical change per commit. - Group related files (e.g., code + its tests) in the same commit. diff --git a/.github/workflows/build-api.yml b/.github/workflows/build-api.yml new file mode 100644 index 0000000..d516b8a --- /dev/null +++ b/.github/workflows/build-api.yml @@ -0,0 +1,83 @@ +name: Build and Push API + +# Migrated from .woodpecker/api.yml on 2026-05-18. GHA builds + pushes to +# DockerHub; Keel polls and rolls the cluster Deployments. No POST to +# Woodpecker — Keel is the deploy mechanism now. + +on: + push: + branches: [master] + paths-ignore: + - 'frontend/**' + - '**.md' + - '.woodpecker/**' + +concurrency: + group: build-api-${{ github.ref }} + cancel-in-progress: true + +env: + IMAGE: viktorbarzin/realestatecrawler + +jobs: + test-and-build: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: '3.13' + cache: 'pip' + + - name: System deps + run: sudo apt-get update && sudo apt-get install -y --no-install-recommends libglib2.0-0 + + - name: Python deps (mirror .woodpecker/api.yml install list) + run: | + python -m pip install --upgrade pip + pip install --quiet \ + pytest pytest-asyncio pytest-cov httpx fakeredis aioresponses \ + fastapi uvicorn sqlmodel sqlalchemy alembic pyjwt cryptography \ + celery redis click aiohttp aiohttp-socks pillow numpy pytesseract \ + opentelemetry-api opentelemetry-sdk opentelemetry-exporter-prometheus \ + opentelemetry-instrumentation-fastapi opentelemetry-instrumentation-sqlalchemy \ + python-dotenv webauthn apprise tenacity prometheus-client \ + email-validator opencv-python-headless tqdm pandas cachetools watchdog + + - name: Unit tests + run: pytest tests/unit/ -v --tb=short + + - name: Integration / regression / e2e tests + run: pytest tests/integration/ tests/regression/ tests/e2e/ tests/test_listing_geojson.py -v --tb=short + + - uses: docker/setup-buildx-action@v3 + + - uses: docker/login-action@v3 + with: + username: viktorbarzin + password: ${{ secrets.DOCKERHUB_TOKEN }} + + # github.sha is the commit hash (40-char hex, controlled by git, not + # external input) — using it via env to satisfy the workflow-injection + # lint hook and keep things uniform. + - id: meta + env: + GIT_SHA: ${{ github.sha }} + run: echo "sha=$(printf '%s' "$GIT_SHA" | cut -c1-8)" >> "$GITHUB_OUTPUT" + + # Pushes both :<8-char-sha> (traceability) and :latest (Keel watches this). + # Deployments reference :latest; Keel detects the digest bump and rolls. + - uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile + target: production + platforms: linux/amd64 + push: true + tags: | + ${{ env.IMAGE }}:${{ steps.meta.outputs.sha }} + ${{ env.IMAGE }}:latest + cache-from: type=gha,scope=api + cache-to: type=gha,scope=api,mode=max diff --git a/.github/workflows/build-frontend.yml b/.github/workflows/build-frontend.yml new file mode 100644 index 0000000..da20aca --- /dev/null +++ b/.github/workflows/build-frontend.yml @@ -0,0 +1,77 @@ +name: Build and Push Frontend + +# Migrated from .woodpecker/frontend.yml on 2026-05-18. GHA builds + pushes +# to DockerHub; Keel polls :latest and rolls the realestate-crawler-ui +# Deployment. The .env.production file carries VITE_MAPBOX_TOKEN at build +# time (Vite reads it from the build context) — same shape as the +# Woodpecker pipeline, just sourced from a GHA secret instead. + +on: + push: + branches: [master] + paths: + - 'frontend/**' + - '.github/workflows/build-frontend.yml' + +concurrency: + group: build-frontend-${{ github.ref }} + cancel-in-progress: true + +env: + IMAGE: viktorbarzin/immoweb + +jobs: + test-and-build: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '24' + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Install deps + working-directory: frontend + run: npm ci + + # Vitest runs all 4 Woodpecker shards in one job — single GHA runner + # is plenty fast and avoids matrix-cost x4. + - name: Vitest + working-directory: frontend + run: npx vitest run --reporter=verbose + + # Writes .env.production for Vite's build-time read. Secret is piped + # via env (not `${{ }}` directly in run:) so shell metacharacters in + # the value, if any, can't break out. + - name: Write .env.production for Vite build + env: + MAPBOX_TOKEN: ${{ secrets.MAPBOX_TOKEN }} + run: printf 'VITE_MAPBOX_TOKEN=%s\n' "$MAPBOX_TOKEN" > frontend/.env.production + + - uses: docker/setup-buildx-action@v3 + + - uses: docker/login-action@v3 + with: + username: viktorbarzin + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - id: meta + env: + GIT_SHA: ${{ github.sha }} + run: echo "sha=$(printf '%s' "$GIT_SHA" | cut -c1-8)" >> "$GITHUB_OUTPUT" + + - uses: docker/build-push-action@v6 + with: + context: frontend + file: frontend/Dockerfile + target: production + platforms: linux/amd64 + push: true + tags: | + ${{ env.IMAGE }}:${{ steps.meta.outputs.sha }} + ${{ env.IMAGE }}:latest + cache-from: type=gha,scope=frontend + cache-to: type=gha,scope=frontend,mode=max diff --git a/.woodpecker/api.yml b/.woodpecker/build-fallback-api.yml similarity index 96% rename from .woodpecker/api.yml rename to .woodpecker/build-fallback-api.yml index 82b6cf1..e447c92 100644 --- a/.woodpecker/api.yml +++ b/.woodpecker/build-fallback-api.yml @@ -1,6 +1,10 @@ +# Fallback pipeline — only fires on manual `deployment` event after GHA +# (.github/workflows/build-api.yml) became the primary builder on +# 2026-05-18. Kept around in case GHA is down and a build needs to ship +# from inside the cluster. `event: deployment` means it never auto-fires +# on push. when: - - event: push - branch: master + - event: deployment clone: git: diff --git a/.woodpecker/frontend.yml b/.woodpecker/build-fallback-frontend.yml similarity index 95% rename from .woodpecker/frontend.yml rename to .woodpecker/build-fallback-frontend.yml index e50ca55..ca166da 100644 --- a/.woodpecker/frontend.yml +++ b/.woodpecker/build-fallback-frontend.yml @@ -1,6 +1,10 @@ +# Fallback pipeline — only fires on manual `deployment` event after GHA +# (.github/workflows/build-frontend.yml) became the primary builder on +# 2026-05-18. Kept around in case GHA is down and a build needs to ship +# from inside the cluster. `event: deployment` means it never auto-fires +# on push. when: - - event: push - branch: master + - event: deployment clone: git: