diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b110858 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,85 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +env: + IMAGE_NAME: broker-sync + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install Poetry + run: pipx install poetry==1.8.4 + - name: Install deps + run: poetry install --no-interaction + - run: poetry run ruff check . + - run: poetry run mypy broker_sync tests + - run: poetry run pytest -q + + build: + runs-on: ubuntu-latest + needs: test + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + outputs: + image_tag: ${{ steps.meta.outputs.sha }} + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - id: meta + run: echo "sha=$(echo ${{ github.sha }} | cut -c1-8)" >> $GITHUB_OUTPUT + - uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile + push: true + platforms: linux/amd64 + tags: | + viktorbarzin/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.sha }} + viktorbarzin/${{ env.IMAGE_NAME }}:latest + cache-from: type=gha + cache-to: type=gha,mode=max + + deploy: + needs: build + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - name: Trigger Woodpecker deploy + env: + # TODO: replace WOODPECKER_REPO_ID with the numeric id assigned when the + # broker-sync repo is registered with Woodpecker. See infra CLAUDE.md + # "Woodpecker API uses numeric repo IDs". + WOODPECKER_REPO_ID: "TBD" + run: | + if [ "$WOODPECKER_REPO_ID" = "TBD" ]; then + echo "Woodpecker repo not yet registered — skipping deploy trigger." + exit 0 + fi + for attempt in 1 2 3; do + STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST \ + "https://ci.viktorbarzin.me/api/repos/${WOODPECKER_REPO_ID}/pipelines" \ + -H "Authorization: Bearer ${{ secrets.WOODPECKER_TOKEN }}" \ + -H "Content-Type: application/json" \ + -d '{"branch":"main","variables":{"IMAGE_TAG":"${{ needs.build.outputs.image_tag }}"}}') + if [ "$STATUS" -ge 200 ] && [ "$STATUS" -lt 300 ]; then + echo "Woodpecker deploy triggered (HTTP $STATUS)" + exit 0 + fi + echo "Attempt $attempt failed (HTTP $STATUS), retrying in 30s..." + sleep 30 + done + echo "Failed to trigger Woodpecker deploy after 3 attempts" + exit 1 diff --git a/.woodpecker/deploy.yml b/.woodpecker/deploy.yml new file mode 100644 index 0000000..9002f1c --- /dev/null +++ b/.woodpecker/deploy.yml @@ -0,0 +1,30 @@ +when: + - event: [manual, push] + +steps: + - name: check-vars + image: alpine + commands: + - "[ -n \"$IMAGE_TAG\" ] || (echo 'IMAGE_TAG not set, skipping deploy'; exit 78)" + + - name: deploy-cronjobs + image: bitnami/kubectl:latest + commands: + # broker-sync runs as CronJobs, not a Deployment. Update each CronJob's image + # so the next scheduled run picks it up. Running CronJob pods are untouched + # (Kubernetes doesn't do rolling updates on CronJob templates). + - "kubectl -n broker-sync set image cronjob/broker-sync-version broker-sync=viktorbarzin/broker-sync:${IMAGE_TAG}" + - "kubectl -n broker-sync set image cronjob/broker-sync-trading212 broker-sync=viktorbarzin/broker-sync:${IMAGE_TAG}" + - "kubectl -n broker-sync set image cronjob/broker-sync-imap broker-sync=viktorbarzin/broker-sync:${IMAGE_TAG}" + - "kubectl -n broker-sync set image cronjob/broker-sync-csv broker-sync=viktorbarzin/broker-sync:${IMAGE_TAG}" + - "kubectl -n broker-sync set image cronjob/broker-sync-fx-reconcile broker-sync=viktorbarzin/broker-sync:${IMAGE_TAG}" + + - name: smoke-test + image: bitnami/kubectl:latest + commands: + # Spawn a one-shot Job from the version CronJob with the new image to prove + # the image is pullable and the CLI entrypoint works end-to-end. + - "kubectl -n broker-sync create job --from=cronjob/broker-sync-version broker-sync-smoke-${CI_COMMIT_SHA:0:8}" + - "kubectl -n broker-sync wait --for=condition=complete --timeout=300s job/broker-sync-smoke-${CI_COMMIT_SHA:0:8}" + - "kubectl -n broker-sync logs job/broker-sync-smoke-${CI_COMMIT_SHA:0:8}" + - "kubectl -n broker-sync delete job broker-sync-smoke-${CI_COMMIT_SHA:0:8}"