Some checks are pending
ci/woodpecker/push/woodpecker Pipeline is running
The woodpeckerci/plugin-docker-buildx was not passing the EXTRAS build arg correctly (commas in the value were likely being parsed as list separators), causing the image to only install dev dependencies instead of all service extras (api, news, sentiment, trading, backtester). Hardcode the pip install extras directly in the Dockerfile rather than relying on the build arg.
205 lines
7.7 KiB
YAML
205 lines
7.7 KiB
YAML
when:
|
|
- event: push
|
|
branch: master
|
|
|
|
clone:
|
|
git:
|
|
image: woodpeckerci/plugin-git
|
|
settings:
|
|
attempts: 5
|
|
backoff: 10s
|
|
|
|
steps:
|
|
- name: test
|
|
image: python:3.12-slim
|
|
commands:
|
|
- |
|
|
if [ -d /woodpecker/pip-cache ]; then
|
|
echo "Restoring pip cache..."
|
|
cp -r /woodpecker/pip-cache .pip-cache
|
|
fi
|
|
- python -m venv .venv
|
|
- >-
|
|
.venv/bin/pip install --quiet --upgrade pip
|
|
- >-
|
|
.venv/bin/pip install --quiet --cache-dir .pip-cache
|
|
".[api,news,sentiment,trading,backtester,dev]"
|
|
- .venv/bin/pytest tests/ -v --tb=short -m "not integration"
|
|
- |
|
|
echo "Saving pip cache..."
|
|
cp -r .pip-cache /woodpecker/pip-cache 2>/dev/null || true
|
|
|
|
- name: build-service-image
|
|
image: woodpeckerci/plugin-docker-buildx
|
|
depends_on:
|
|
- test
|
|
settings:
|
|
username: viktorbarzin
|
|
password:
|
|
from_secret: dockerhub-token
|
|
repo: viktorbarzin/trading-bot-service
|
|
dockerfile: docker/Dockerfile.service
|
|
context: .
|
|
build_args:
|
|
- SERVICE_MODULE=api_gateway
|
|
tags:
|
|
- "${CI_PIPELINE_NUMBER}"
|
|
- latest
|
|
|
|
- name: build-dashboard-image
|
|
image: woodpeckerci/plugin-docker-buildx
|
|
depends_on:
|
|
- test
|
|
settings:
|
|
username: viktorbarzin
|
|
password:
|
|
from_secret: dockerhub-token
|
|
repo: viktorbarzin/trading-bot-dashboard
|
|
dockerfile: docker/Dockerfile.dashboard
|
|
context: .
|
|
build_args:
|
|
- NGINX_CONF=docker/nginx-k8s.conf
|
|
tags:
|
|
- "${CI_PIPELINE_NUMBER}"
|
|
- latest
|
|
|
|
- name: update-deployment
|
|
image: alpine
|
|
depends_on:
|
|
- build-service-image
|
|
- build-dashboard-image
|
|
commands:
|
|
- apk add --no-cache curl jq
|
|
- |
|
|
TOKEN=$$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
|
|
SERVICE_IMAGE="viktorbarzin/trading-bot-service:${CI_PIPELINE_NUMBER}"
|
|
DASHBOARD_IMAGE="viktorbarzin/trading-bot-dashboard:${CI_PIPELINE_NUMBER}"
|
|
RESTART_AT=$$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
API="https://kubernetes:6443/apis/apps/v1/namespaces/trading-bot/deployments"
|
|
|
|
# --- trading-bot-frontend: 2 containers ---
|
|
echo "Patching trading-bot-frontend..."
|
|
curl -sf -X PATCH "$$API/trading-bot-frontend" \
|
|
-H "Authorization: Bearer $$TOKEN" \
|
|
-H "Content-Type: application/strategic-merge-patch+json" \
|
|
-k -d "{
|
|
\"spec\":{
|
|
\"paused\":null,
|
|
\"template\":{
|
|
\"metadata\":{\"annotations\":{\"kubectl.kubernetes.io/restartedAt\":\"$$RESTART_AT\"}},
|
|
\"spec\":{\"containers\":[
|
|
{\"name\":\"dashboard\",\"image\":\"$$DASHBOARD_IMAGE\"},
|
|
{\"name\":\"api-gateway\",\"image\":\"$$SERVICE_IMAGE\"}
|
|
]}
|
|
}
|
|
}
|
|
}" | jq '{name: .metadata.name, generation: .metadata.generation}'
|
|
|
|
# --- trading-bot-workers: 6 containers ---
|
|
echo "Patching trading-bot-workers..."
|
|
curl -sf -X PATCH "$$API/trading-bot-workers" \
|
|
-H "Authorization: Bearer $$TOKEN" \
|
|
-H "Content-Type: application/strategic-merge-patch+json" \
|
|
-k -d "{
|
|
\"spec\":{
|
|
\"paused\":null,
|
|
\"template\":{
|
|
\"metadata\":{\"annotations\":{\"kubectl.kubernetes.io/restartedAt\":\"$$RESTART_AT\"}},
|
|
\"spec\":{\"containers\":[
|
|
{\"name\":\"news-fetcher\",\"image\":\"$$SERVICE_IMAGE\"},
|
|
{\"name\":\"sentiment-analyzer\",\"image\":\"$$SERVICE_IMAGE\"},
|
|
{\"name\":\"signal-generator\",\"image\":\"$$SERVICE_IMAGE\"},
|
|
{\"name\":\"trade-executor\",\"image\":\"$$SERVICE_IMAGE\"},
|
|
{\"name\":\"learning-engine\",\"image\":\"$$SERVICE_IMAGE\"},
|
|
{\"name\":\"market-data\",\"image\":\"$$SERVICE_IMAGE\"}
|
|
]}
|
|
}
|
|
}
|
|
}" | jq '{name: .metadata.name, generation: .metadata.generation}'
|
|
|
|
- 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_SERVICE="viktorbarzin/trading-bot-service:${CI_PIPELINE_NUMBER}"
|
|
EXPECTED_DASHBOARD="viktorbarzin/trading-bot-dashboard:${CI_PIPELINE_NUMBER}"
|
|
BASE_API="https://kubernetes:6443/api/v1/namespaces/trading-bot/pods"
|
|
DEPLOY_API="https://kubernetes:6443/apis/apps/v1/namespaces/trading-bot/deployments"
|
|
|
|
for DEPLOY in trading-bot-frontend trading-bot-workers; do
|
|
echo "Verifying $$DEPLOY..."
|
|
PODS_API="$$BASE_API?labelSelector=app%3D$$DEPLOY"
|
|
|
|
if [ "$$DEPLOY" = "trading-bot-frontend" ]; then
|
|
EXPECTED_IMAGE="$$EXPECTED_DASHBOARD"
|
|
else
|
|
EXPECTED_IMAGE="$$EXPECTED_SERVICE"
|
|
fi
|
|
|
|
DEPLOY_STATUS=$$(curl -sfk "$$DEPLOY_API/$$DEPLOY" \
|
|
-H "Authorization: Bearer $$TOKEN" \
|
|
-H "Accept: application/json")
|
|
echo " Deployment status: replicas=$$(echo "$$DEPLOY_STATUS" | jq -r '.status.replicas // 0') updated=$$(echo "$$DEPLOY_STATUS" | jq -r '.status.updatedReplicas // 0') ready=$$(echo "$$DEPLOY_STATUS" | jq -r '.status.readyReplicas // 0')"
|
|
|
|
FOUND=0
|
|
for i in $$(seq 1 60); do
|
|
RAW=$$(curl -sfk "$$PODS_API" \
|
|
-H "Authorization: Bearer $$TOKEN" \
|
|
-H "Accept: application/json")
|
|
|
|
if [ "$$i" -eq 1 ] || [ "$$i" -eq 10 ] || [ "$$i" -eq 30 ]; then
|
|
echo " DEBUG (attempt $$i): All pods for $$DEPLOY:"
|
|
echo "$$RAW" | jq -r '[.items[] | {
|
|
name: .metadata.name,
|
|
ready: ([.status.containerStatuses[]? | .ready] | all),
|
|
phase: .status.phase,
|
|
restarts: ([.status.containerStatuses[]? | .restartCount] | add // 0)
|
|
}] | .[] | " \(.name) ready=\(.ready) phase=\(.phase) restarts=\(.restarts)"' 2>/dev/null || echo " (no pods or parse error)"
|
|
fi
|
|
|
|
RESULT=$$(echo "$$RAW" | \
|
|
jq --arg img "$$EXPECTED_IMAGE" '[.items[] | select(
|
|
([.status.containerStatuses[]? | .ready] | all) and
|
|
(.spec.containers[]? | .image | endswith($$img))
|
|
) | {name: .metadata.name, 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) started=\(.started)"'
|
|
echo "$$DEPLOY is live!"
|
|
FOUND=1
|
|
break
|
|
fi
|
|
|
|
sleep 5
|
|
done
|
|
|
|
if [ "$$FOUND" -ne 1 ]; then
|
|
echo " FINAL DEBUG: All pods for $$DEPLOY:"
|
|
echo "$$RAW" | jq -r '[.items[] | {
|
|
name: .metadata.name,
|
|
ready: ([.status.containerStatuses[]? | .ready] | all),
|
|
phase: .status.phase,
|
|
restarts: ([.status.containerStatuses[]? | .restartCount] | add // 0)
|
|
}] | .[] | " \(.name) ready=\(.ready) phase=\(.phase) restarts=\(.restarts)"' 2>/dev/null || echo " (no pods or parse error)"
|
|
echo "ERROR: No new ready pod for $$DEPLOY with image $$EXPECTED_IMAGE appeared within 5 minutes"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
- name: slack
|
|
image: plugins/slack
|
|
depends_on:
|
|
- verify-deploy
|
|
settings:
|
|
webhook:
|
|
from_secret: slack-webhook-url
|
|
channel: general
|
|
when:
|
|
- status: [success, failure]
|