Same pattern as the frontend pipeline: build pushes to a staging tag (build-N) in parallel with tests. Tests split into unit and integration shards using a shared venv from the workspace. Publish step uses skopeo to copy the manifest to final tags (:N, :latest, :builder) only after all tests and the build succeed. Named the final Dockerfile stage 'production' so target skips the test stage.
313 lines
11 KiB
YAML
313 lines
11 KiB
YAML
kind: pipeline
|
|
type: kubernetes
|
|
name: frontend
|
|
|
|
clone:
|
|
disable: true
|
|
|
|
concurrency:
|
|
limit: 1
|
|
|
|
timeout: 20
|
|
|
|
trigger:
|
|
branch:
|
|
- master
|
|
event:
|
|
- push
|
|
|
|
steps:
|
|
- name: clone
|
|
image: alpine/git
|
|
commands:
|
|
- |
|
|
for i in 1 2 3 4 5; do
|
|
git clone --depth=50 "$DRONE_REMOTE_URL" . && exit 0
|
|
echo "Clone attempt $i failed, retrying in 5s..."
|
|
sleep 5
|
|
done
|
|
echo "Clone failed after 5 attempts"
|
|
exit 1
|
|
- git checkout "$DRONE_COMMIT"
|
|
|
|
- name: install-frontend-deps
|
|
image: node:24-alpine
|
|
depends_on:
|
|
- clone
|
|
environment:
|
|
NODE_OPTIONS: "--max-old-space-size=1024"
|
|
commands:
|
|
- cd frontend && npm ci
|
|
|
|
- name: test-shard-1
|
|
image: node:24-alpine
|
|
depends_on:
|
|
- install-frontend-deps
|
|
environment:
|
|
NODE_OPTIONS: "--max-old-space-size=1024"
|
|
commands:
|
|
- cd frontend && npx vitest run --reporter=verbose --shard=1/4
|
|
|
|
- name: test-shard-2
|
|
image: node:24-alpine
|
|
depends_on:
|
|
- install-frontend-deps
|
|
environment:
|
|
NODE_OPTIONS: "--max-old-space-size=1024"
|
|
commands:
|
|
- cd frontend && npx vitest run --reporter=verbose --shard=2/4
|
|
|
|
- name: test-shard-3
|
|
image: node:24-alpine
|
|
depends_on:
|
|
- install-frontend-deps
|
|
environment:
|
|
NODE_OPTIONS: "--max-old-space-size=1024"
|
|
commands:
|
|
- cd frontend && npx vitest run --reporter=verbose --shard=3/4
|
|
|
|
- name: test-shard-4
|
|
image: node:24-alpine
|
|
depends_on:
|
|
- install-frontend-deps
|
|
environment:
|
|
NODE_OPTIONS: "--max-old-space-size=1024"
|
|
commands:
|
|
- cd frontend && npx vitest run --reporter=verbose --shard=4/4
|
|
|
|
- name: build-frontend-image
|
|
image: plugins/kaniko
|
|
depends_on:
|
|
- clone
|
|
resources:
|
|
limits:
|
|
memory: 2048MiB
|
|
settings:
|
|
username: viktorbarzin
|
|
password:
|
|
from_secret: dockerhub-token
|
|
repo: viktorbarzin/immoweb
|
|
dockerfile: frontend/Dockerfile
|
|
context: frontend
|
|
target: production
|
|
enable_cache: true
|
|
cache_repo: viktorbarzin/immoweb-cache
|
|
tags:
|
|
- "build-${DRONE_BUILD_NUMBER}"
|
|
|
|
- name: publish-frontend-image
|
|
image: alpine
|
|
depends_on:
|
|
- test-shard-1
|
|
- test-shard-2
|
|
- test-shard-3
|
|
- test-shard-4
|
|
- build-frontend-image
|
|
environment:
|
|
DOCKERHUB_TOKEN:
|
|
from_secret: dockerhub-token
|
|
commands:
|
|
- apk add --no-cache skopeo
|
|
- 'skopeo copy --src-creds "viktorbarzin:$DOCKERHUB_TOKEN" --dest-creds "viktorbarzin:$DOCKERHUB_TOKEN" "docker://docker.io/viktorbarzin/immoweb:build-${DRONE_BUILD_NUMBER}" "docker://docker.io/viktorbarzin/immoweb:${DRONE_BUILD_NUMBER}"'
|
|
- 'skopeo copy --src-creds "viktorbarzin:$DOCKERHUB_TOKEN" --dest-creds "viktorbarzin:$DOCKERHUB_TOKEN" "docker://docker.io/viktorbarzin/immoweb:build-${DRONE_BUILD_NUMBER}" "docker://docker.io/viktorbarzin/immoweb:latest"'
|
|
|
|
- name: Update deployment
|
|
image: alpine
|
|
depends_on:
|
|
- publish-frontend-image
|
|
commands:
|
|
- apk add curl
|
|
- 'curl -s -X PATCH "https://kubernetes:6443/apis/apps/v1/namespaces/realestate-crawler/deployments/realestate-crawler-ui" -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" -H "Content-Type: application/json-patch+json" -k -d ''[{"op":"replace","path":"/spec/template/spec/containers/0/image","value":"viktorbarzin/immoweb:''"$DRONE_BUILD_NUMBER"''"}]'' | head'
|
|
|
|
- name: verify-deploy
|
|
image: alpine
|
|
depends_on:
|
|
- Update deployment
|
|
commands:
|
|
- apk add --no-cache curl jq
|
|
- |
|
|
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
|
|
EXPECTED_IMAGE="viktorbarzin/immoweb:${DRONE_BUILD_NUMBER}"
|
|
PODS_API="https://kubernetes:6443/api/v1/namespaces/realestate-crawler/pods?labelSelector=app%3Drealestate-crawler-ui"
|
|
|
|
for i in $(seq 1 60); do
|
|
RESULT=$(curl -sfk "$PODS_API" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Accept: application/json" | \
|
|
jq --arg img "$EXPECTED_IMAGE" '[.items[] | select(
|
|
(now - (.status.startTime | fromdateiso8601)) < 60 and
|
|
(.status.containerStatuses[]? | .ready == true) and
|
|
(.spec.containers[]? | .image == $img)
|
|
) | {name: .metadata.name, age: (now - (.status.startTime | fromdateiso8601) | floor), image: .spec.containers[0].image, started: .status.startTime}]')
|
|
|
|
COUNT=$(echo "$RESULT" | jq 'length')
|
|
echo "Attempt $i/60: $COUNT pod(s) younger than 60s, ready, running $EXPECTED_IMAGE"
|
|
|
|
if [ "$COUNT" -gt 0 ]; then
|
|
echo "$RESULT" | jq -r '.[] | " \(.name) age=\(.age)s image=\(.image) started=\(.started)"'
|
|
echo "New pod is live!"
|
|
exit 0
|
|
fi
|
|
|
|
sleep 5
|
|
done
|
|
|
|
echo "ERROR: No new ready pod with image $EXPECTED_IMAGE appeared within 5 minutes"
|
|
exit 1
|
|
|
|
---
|
|
kind: pipeline
|
|
type: kubernetes
|
|
name: api
|
|
|
|
clone:
|
|
disable: true
|
|
|
|
concurrency:
|
|
limit: 1
|
|
|
|
timeout: 20
|
|
|
|
trigger:
|
|
branch:
|
|
- master
|
|
event:
|
|
- push
|
|
|
|
steps:
|
|
- name: clone
|
|
image: alpine/git
|
|
commands:
|
|
- |
|
|
for i in 1 2 3 4 5; do
|
|
git clone --depth=50 "$DRONE_REMOTE_URL" . && exit 0
|
|
echo "Clone attempt $i failed, retrying in 5s..."
|
|
sleep 5
|
|
done
|
|
echo "Clone failed after 5 attempts"
|
|
exit 1
|
|
- git checkout "$DRONE_COMMIT"
|
|
|
|
- name: install-api-deps
|
|
image: python:3.13-slim
|
|
depends_on:
|
|
- clone
|
|
commands:
|
|
- apt-get update && apt-get install -y --no-install-recommends libglib2.0-0
|
|
- python -m venv .venv
|
|
- .venv/bin/pip install --quiet --upgrade pip
|
|
- >-
|
|
.venv/bin/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: test-unit
|
|
image: python:3.13-slim
|
|
depends_on:
|
|
- install-api-deps
|
|
commands:
|
|
- apt-get update && apt-get install -y --no-install-recommends libglib2.0-0
|
|
- .venv/bin/pytest tests/unit/ -v --tb=short
|
|
|
|
- name: test-integration
|
|
image: python:3.13-slim
|
|
depends_on:
|
|
- install-api-deps
|
|
commands:
|
|
- apt-get update && apt-get install -y --no-install-recommends libglib2.0-0
|
|
- .venv/bin/pytest tests/integration/ tests/regression/ tests/e2e/ tests/test_listing_geojson.py -v --tb=short
|
|
|
|
- name: build-api-image
|
|
image: plugins/docker
|
|
depends_on:
|
|
- clone
|
|
environment:
|
|
DOCKER_BUILDKIT: 1
|
|
settings:
|
|
username: viktorbarzin
|
|
password:
|
|
from_secret: dockerhub-token
|
|
repo: viktorbarzin/realestatecrawler
|
|
dockerfile: Dockerfile
|
|
context: .
|
|
target: production
|
|
cache_from:
|
|
- viktorbarzin/realestatecrawler:latest
|
|
- viktorbarzin/realestatecrawler:builder
|
|
tags:
|
|
- "build-${DRONE_BUILD_NUMBER}"
|
|
|
|
- name: publish-api-image
|
|
image: alpine
|
|
depends_on:
|
|
- test-unit
|
|
- test-integration
|
|
- build-api-image
|
|
environment:
|
|
DOCKERHUB_TOKEN:
|
|
from_secret: dockerhub-token
|
|
commands:
|
|
- apk add --no-cache skopeo
|
|
- 'skopeo copy --src-creds "viktorbarzin:$DOCKERHUB_TOKEN" --dest-creds "viktorbarzin:$DOCKERHUB_TOKEN" "docker://docker.io/viktorbarzin/realestatecrawler:build-${DRONE_BUILD_NUMBER}" "docker://docker.io/viktorbarzin/realestatecrawler:${DRONE_BUILD_NUMBER}"'
|
|
- 'skopeo copy --src-creds "viktorbarzin:$DOCKERHUB_TOKEN" --dest-creds "viktorbarzin:$DOCKERHUB_TOKEN" "docker://docker.io/viktorbarzin/realestatecrawler:build-${DRONE_BUILD_NUMBER}" "docker://docker.io/viktorbarzin/realestatecrawler:latest"'
|
|
- 'skopeo copy --src-creds "viktorbarzin:$DOCKERHUB_TOKEN" --dest-creds "viktorbarzin:$DOCKERHUB_TOKEN" "docker://docker.io/viktorbarzin/realestatecrawler:build-${DRONE_BUILD_NUMBER}" "docker://docker.io/viktorbarzin/realestatecrawler:builder"'
|
|
|
|
- name: Update deployment
|
|
image: alpine
|
|
depends_on:
|
|
- publish-api-image
|
|
commands:
|
|
- apk add curl
|
|
- 'curl -s -X PATCH "https://kubernetes:6443/apis/apps/v1/namespaces/realestate-crawler/deployments/realestate-crawler-api" -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" -H "Content-Type: application/json-patch+json" -k -d ''[{"op":"replace","path":"/spec/template/spec/containers/0/image","value":"viktorbarzin/realestatecrawler:''"$DRONE_BUILD_NUMBER"''"}]'' | head'
|
|
- 'curl -s -X PATCH "https://kubernetes:6443/apis/apps/v1/namespaces/realestate-crawler/deployments/realestate-crawler-celery" -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" -H "Content-Type: application/json-patch+json" -k -d ''[{"op":"replace","path":"/spec/template/spec/containers/0/image","value":"viktorbarzin/realestatecrawler:''"$DRONE_BUILD_NUMBER"''"}]'' | head'
|
|
- 'curl -s -X PATCH "https://kubernetes:6443/apis/apps/v1/namespaces/realestate-crawler/deployments/realestate-crawler-celery-beat" -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" -H "Content-Type: application/json-patch+json" -k -d ''[{"op":"replace","path":"/spec/template/spec/containers/0/image","value":"viktorbarzin/realestatecrawler:''"$DRONE_BUILD_NUMBER"''"}]'' | head'
|
|
|
|
- name: verify-deploy
|
|
image: alpine
|
|
depends_on:
|
|
- Update deployment
|
|
commands:
|
|
- apk add --no-cache curl jq
|
|
- |
|
|
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
|
|
EXPECTED_IMAGE="viktorbarzin/realestatecrawler:${DRONE_BUILD_NUMBER}"
|
|
BASE_API="https://kubernetes:6443/api/v1/namespaces/realestate-crawler/pods"
|
|
|
|
for DEPLOY in realestate-crawler-api realestate-crawler-celery realestate-crawler-celery-beat; do
|
|
echo "Verifying $DEPLOY..."
|
|
PODS_API="$BASE_API?labelSelector=app%3D$DEPLOY"
|
|
|
|
FOUND=0
|
|
for i in $(seq 1 60); do
|
|
RESULT=$(curl -sfk "$PODS_API" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Accept: application/json" | \
|
|
jq --arg img "$EXPECTED_IMAGE" '[.items[] | select(
|
|
(now - (.status.startTime | fromdateiso8601)) < 60 and
|
|
(.status.containerStatuses[]? | .ready == true) and
|
|
(.spec.containers[]? | .image == $img)
|
|
) | {name: .metadata.name, age: (now - (.status.startTime | fromdateiso8601) | floor), image: .spec.containers[0].image, started: .status.startTime}]')
|
|
|
|
COUNT=$(echo "$RESULT" | jq 'length' 2>/dev/null || echo 0)
|
|
echo " Attempt $i/60: $COUNT pod(s) younger than 60s, ready, running $EXPECTED_IMAGE"
|
|
|
|
if [ "$COUNT" -gt 0 ] 2>/dev/null; then
|
|
echo "$RESULT" | jq -r '.[] | " \(.name) age=\(.age)s image=\(.image) started=\(.started)"'
|
|
echo "$DEPLOY is live!"
|
|
FOUND=1
|
|
break
|
|
fi
|
|
|
|
sleep 5
|
|
done
|
|
|
|
if [ "$FOUND" -ne 1 ]; then
|
|
echo "ERROR: No new ready pod for $DEPLOY with image $EXPECTED_IMAGE appeared within 5 minutes"
|
|
exit 1
|
|
fi
|
|
done
|