Kubernetes may normalize image references to include docker.io/ prefix (e.g., docker.io/viktorbarzin/realestatecrawler:356), causing exact string match to fail. Use endswith() instead. Also add debug output on first attempt to show actual pod images and readiness state.
327 lines
12 KiB
YAML
327 lines
12 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
|
|
RAW=$(curl -sfk "$PODS_API" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Accept: application/json")
|
|
|
|
# Debug: show all pod images and status on first attempt
|
|
if [ "$i" -eq 1 ]; then
|
|
echo "DEBUG: All pods for realestate-crawler-ui:"
|
|
echo "$RAW" | jq -r '[.items[] | {name: .metadata.name, image: .spec.containers[0].image, ready: (.status.containerStatuses[]? | .ready), phase: .status.phase}] | .[] | " \(.name) image=\(.image) ready=\(.ready) phase=\(.phase)"' 2>/dev/null || echo " (no pods found)"
|
|
fi
|
|
|
|
RESULT=$(echo "$RAW" | \
|
|
jq --arg img "$EXPECTED_IMAGE" '[.items[] | select(
|
|
(.status.containerStatuses[]? | .ready == true) and
|
|
(.spec.containers[]? | .image | endswith($img))
|
|
) | {name: .metadata.name, image: .spec.containers[0].image, started: .status.startTime}]')
|
|
|
|
COUNT=$(echo "$RESULT" | jq 'length')
|
|
echo "Attempt $i/60: $COUNT pod(s) ready with image matching $EXPECTED_IMAGE"
|
|
|
|
if [ "$COUNT" -gt 0 ]; then
|
|
echo "$RESULT" | jq -r '.[] | " \(.name) 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
|
|
RAW=$(curl -sfk "$PODS_API" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Accept: application/json")
|
|
|
|
# Debug: show all pod images and status on first attempt
|
|
if [ "$i" -eq 1 ]; then
|
|
echo " DEBUG: All pods for $DEPLOY:"
|
|
echo "$RAW" | jq -r '[.items[] | {name: .metadata.name, image: .spec.containers[0].image, ready: (.status.containerStatuses[]? | .ready), phase: .status.phase}] | .[] | " \(.name) image=\(.image) ready=\(.ready) phase=\(.phase)"' 2>/dev/null || echo " (no pods found)"
|
|
fi
|
|
|
|
RESULT=$(echo "$RAW" | \
|
|
jq --arg img "$EXPECTED_IMAGE" '[.items[] | select(
|
|
(.status.containerStatuses[]? | .ready == true) and
|
|
(.spec.containers[]? | .image | endswith($img))
|
|
) | {name: .metadata.name, 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) ready with image matching $EXPECTED_IMAGE"
|
|
|
|
if [ "$COUNT" -gt 0 ] 2>/dev/null; then
|
|
echo "$RESULT" | jq -r '.[] | " \(.name) 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
|