Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
The publish-images step used an alpine container to run skopeo for re-tagging, but intermittent DNS failures prevent apk from installing packages. Instead, have the buildx plugin push with both the pipeline number tag and latest tag directly, eliminating the extra step.
206 lines
7.7 KiB
YAML
206 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:
|
|
- EXTRAS=api,news,sentiment,trading,backtester
|
|
- 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]
|