From 1dd0c25cbc2cde5c7129510bb1dc2c5927bcb627 Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Wed, 25 Feb 2026 00:43:01 +0000 Subject: [PATCH] fix: escape shell variables from Woodpecker CI substitution Woodpecker pre-processes ${VAR} syntax as CI variables, replacing undefined ones with empty strings. Use $$ escaping for shell variables to prevent Woodpecker from consuming them. The ${REPO} variable in the skopeo publish step was being replaced with empty string. --- .woodpecker.yml | 102 ++++++++++++++++++++++++------------------------ 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/.woodpecker.yml b/.woodpecker.yml index 10553a7..9116528 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -75,15 +75,15 @@ steps: - | for REPO in trading-bot-service trading-bot-dashboard; do skopeo copy \ - --src-creds "viktorbarzin:$DOCKERHUB_TOKEN" \ - --dest-creds "viktorbarzin:$DOCKERHUB_TOKEN" \ - "docker://docker.io/viktorbarzin/${REPO}:build-${CI_PIPELINE_NUMBER}" \ - "docker://docker.io/viktorbarzin/${REPO}:${CI_PIPELINE_NUMBER}" + --src-creds "viktorbarzin:$$DOCKERHUB_TOKEN" \ + --dest-creds "viktorbarzin:$$DOCKERHUB_TOKEN" \ + "docker://docker.io/viktorbarzin/$${REPO}:build-${CI_PIPELINE_NUMBER}" \ + "docker://docker.io/viktorbarzin/$${REPO}:${CI_PIPELINE_NUMBER}" skopeo copy \ - --src-creds "viktorbarzin:$DOCKERHUB_TOKEN" \ - --dest-creds "viktorbarzin:$DOCKERHUB_TOKEN" \ - "docker://docker.io/viktorbarzin/${REPO}:build-${CI_PIPELINE_NUMBER}" \ - "docker://docker.io/viktorbarzin/${REPO}:latest" + --src-creds "viktorbarzin:$$DOCKERHUB_TOKEN" \ + --dest-creds "viktorbarzin:$$DOCKERHUB_TOKEN" \ + "docker://docker.io/viktorbarzin/$${REPO}:build-${CI_PIPELINE_NUMBER}" \ + "docker://docker.io/viktorbarzin/$${REPO}:latest" done - name: update-deployment @@ -93,25 +93,25 @@ steps: commands: - apk add --no-cache curl jq - | - TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) + 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) + 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" \ + 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\"}}, + \"metadata\":{\"annotations\":{\"kubectl.kubernetes.io/restartedAt\":\"$$RESTART_AT\"}}, \"spec\":{\"containers\":[ - {\"name\":\"dashboard\",\"image\":\"$DASHBOARD_IMAGE\"}, - {\"name\":\"api-gateway\",\"image\":\"$SERVICE_IMAGE\"} + {\"name\":\"dashboard\",\"image\":\"$$DASHBOARD_IMAGE\"}, + {\"name\":\"api-gateway\",\"image\":\"$$SERVICE_IMAGE\"} ]} } } @@ -119,21 +119,21 @@ steps: # --- trading-bot-workers: 6 containers --- echo "Patching trading-bot-workers..." - curl -sf -X PATCH "$API/trading-bot-workers" \ - -H "Authorization: Bearer $TOKEN" \ + 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\"}}, + \"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\"} + {\"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\"} ]} } } @@ -146,36 +146,36 @@ steps: commands: - apk add --no-cache curl jq - | - TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) + 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" + echo "Verifying $$DEPLOY..." + PODS_API="$$BASE_API?labelSelector=app%3D$$DEPLOY" - if [ "$DEPLOY" = "trading-bot-frontend" ]; then - EXPECTED_IMAGE="$EXPECTED_DASHBOARD" + if [ "$$DEPLOY" = "trading-bot-frontend" ]; then + EXPECTED_IMAGE="$$EXPECTED_DASHBOARD" else - EXPECTED_IMAGE="$EXPECTED_SERVICE" + EXPECTED_IMAGE="$$EXPECTED_SERVICE" fi - DEPLOY_STATUS=$(curl -sfk "$DEPLOY_API/$DEPLOY" \ - -H "Authorization: Bearer $TOKEN" \ + 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')" + 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" \ + 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[] | { + 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, @@ -183,18 +183,18 @@ steps: }] | .[] | " \(.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( + RESULT=$$(echo "$$RAW" | \ + jq --arg img "$$EXPECTED_IMAGE" '[.items[] | select( ([.status.containerStatuses[]? | .ready] | all) and - (.spec.containers[]? | .image | endswith($img)) + (.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" + 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!" + if [ "$$COUNT" -gt 0 ] 2>/dev/null; then + echo "$$RESULT" | jq -r '.[] | " \(.name) started=\(.started)"' + echo "$$DEPLOY is live!" FOUND=1 break fi @@ -202,15 +202,15 @@ steps: sleep 5 done - if [ "$FOUND" -ne 1 ]; then - echo " FINAL DEBUG: All pods for $DEPLOY:" - echo "$RAW" | jq -r '[.items[] | { + 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" + echo "ERROR: No new ready pod for $$DEPLOY with image $$EXPECTED_IMAGE appeared within 5 minutes" exit 1 fi done