trading/.woodpecker.yml
Viktor Barzin 5955a5a86d
Some checks are pending
ci/woodpecker/push/woodpecker Pipeline is running
fix: hardcode pip extras in Dockerfile to avoid buildx arg parsing issues
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.
2026-02-25 22:27:15 +00:00

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]