ci: build-containers зависит от build-contracts через workflow_run
Раньше при релизе (`chore(release): publish` + git tag) запускались параллельно `build-contracts.yaml` (push веток) и `build-containers.yaml` (push тэгов). Поскольку контейнеры собираются ~8 минут, а контракты ~11, build-containers финишил первым и слал webhook на тестнет за 2-3 минуты до того, как build-contracts успевал запушить новый `dicoop/contracts:dev` в DockerHub. Ансибл `setup-contracts.yaml` подтягивал ПРЕДЫДУЩИЙ образ и через `cleos set contract` перетирал чейн старым wasm. Инцидент 2026-05-13: walletop-фикс ledger2 (коммит2e3410b830), вручную задеплоенный 12-05, был откачен ансиблом ровно по этой причине — ансибл подтянул контракты сборки 12-05 04:54 (shade0f6c79, до моего фикса). Изменения: - `build-contracts.yaml` дополнительно триггерится на push любого тэга (без path-фильтра): нужен unconditional запуск, чтобы у workflow_run всегда был upstream-завершение даже когда коммит ничего не правит в `components/contracts/cpp/**`. - `build-containers.yaml` переключён с `push: tags` на `workflow_run: Build contracts container completed`. Внутри: резолв тэга через `git describe --tags --exact-match $head_sha` — если на коммите тэга нет (обычный push в ветку без релиза), no-op. Если есть — собирает контейнеры и шлёт webhook ровно как раньше, но с гарантией что `dicoop/contracts:<branch>` уже свежий. Важно: workflow_run-триггер берёт definition из default-ветки (main), поэтому новый build-containers.yaml начнёт работать только после мержа этого коммита в main. До тех пор сохраняется старое поведение dev-ветки (workflow_run от build-contracts на dev запустит build-containers из main; если там старая версия — всё ещё через push: tags).
This commit is contained in:
@@ -1,97 +1,130 @@
|
||||
name: Build Docker Images
|
||||
|
||||
# Триггерится по успешному завершению `Build contracts container`. Это
|
||||
# гарантирует, что к моменту webhook'а на тестнет/прод образ
|
||||
# `dicoop/contracts:<branch>` уже обновлён в DockerHub.
|
||||
#
|
||||
# Раньше workflow висел на `push: tags`, и при релизе (`chore(release):
|
||||
# publish` + git tag) запускался параллельно с build-contracts. Поскольку
|
||||
# build-containers (~8 мин) короче build-contracts (~11 мин), webhook
|
||||
# отлетал раньше окончания build-contracts на 2-3 минуты — ансибл на
|
||||
# тестнете подтягивал ПРЕДЫДУЩИЙ образ контрактов и перетирал чейн
|
||||
# старым wasm. Инцидент 2026-05-13 (ledger2 фикс walletop sweep'а
|
||||
# откатился ансиблом).
|
||||
#
|
||||
# Теперь: build-contracts триггерится и по push веток, и по push тэгов
|
||||
# (см. tags в build-contracts.yaml). После его success этот workflow
|
||||
# резолвит — есть ли тэг на коммите, и если есть — собирает контейнеры
|
||||
# и шлёт webhook. Если тэга нет (обычный push в ветку без релиза) —
|
||||
# делаем no-op.
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
workflow_run:
|
||||
workflows: ["Build contracts container"]
|
||||
types: [completed]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
- name: Checkout repository at triggering SHA
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
ref: ${{ github.event.workflow_run.head_sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Debug info
|
||||
|
||||
- name: Resolve tag for triggering commit
|
||||
id: resolve_tag
|
||||
run: |
|
||||
echo "Текущая ветка:"
|
||||
git branch --show-current
|
||||
HEAD_SHA="${{ github.event.workflow_run.head_sha }}"
|
||||
# `git describe --exact-match` отдаёт имя тэга, лежащего ровно
|
||||
# на этом коммите; код 128 если тэга нет — тогда выходим без
|
||||
# сборки контейнеров (push в ветку без релизного тэга).
|
||||
TAG_NAME=$(git describe --tags --exact-match "$HEAD_SHA" 2>/dev/null || true)
|
||||
if [ -z "$TAG_NAME" ]; then
|
||||
echo "На коммите $HEAD_SHA тэга нет — релиза не было, контейнеры не собираем."
|
||||
echo "has_tag=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
echo "Найден тэг: $TAG_NAME"
|
||||
echo "has_tag=true" >> $GITHUB_OUTPUT
|
||||
echo "DOCKER_TAG=$TAG_NAME" >> $GITHUB_ENV
|
||||
|
||||
# Проверяем, является ли тег продакшн-тегом (не содержит alpha, beta, rc и т.д.)
|
||||
if [[ ! $TAG_NAME =~ -(alpha|beta|rc|test) ]]; then
|
||||
echo "IS_PRODUCTION_TAG=true" >> $GITHUB_ENV
|
||||
echo "Это продакшн тег, будем добавлять latest"
|
||||
else
|
||||
echo "IS_PRODUCTION_TAG=false" >> $GITHUB_ENV
|
||||
echo "Это не продакшн тег, latest не добавляем"
|
||||
fi
|
||||
|
||||
- name: Debug info
|
||||
if: steps.resolve_tag.outputs.has_tag == 'true'
|
||||
run: |
|
||||
echo "Триггер: workflow_run от 'Build contracts container'"
|
||||
echo "Триггерящий коммит: ${{ github.event.workflow_run.head_sha }}"
|
||||
echo "Ветка/ref триггера: ${{ github.event.workflow_run.head_branch }}"
|
||||
echo "Резолвенный тэг: ${{ env.DOCKER_TAG }}"
|
||||
echo "Последние коммиты:"
|
||||
git log -n 3 --oneline
|
||||
echo "Проверяем файлы в директории components/desktop/src-ssr:"
|
||||
ls -la components/desktop/src-ssr/ || echo "Директория не найдена!"
|
||||
echo "Проверяем файлы в middlewares:"
|
||||
ls -la components/desktop/src-ssr/middlewares/ || echo "Директория middlewares не найдена!"
|
||||
|
||||
- name: Set docker tags
|
||||
run: |
|
||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
TAG_NAME=${GITHUB_REF#refs/tags/}
|
||||
echo "DOCKER_TAG=$TAG_NAME" >> $GITHUB_ENV
|
||||
|
||||
# Проверяем, является ли тег продакшн-тегом (не содержит alpha, beta, rc и т.д.)
|
||||
if [[ ! $TAG_NAME =~ -(alpha|beta|rc|test) ]]; then
|
||||
echo "IS_PRODUCTION_TAG=true" >> $GITHUB_ENV
|
||||
echo "Это продакшн тег, будем добавлять latest"
|
||||
else
|
||||
echo "IS_PRODUCTION_TAG=false" >> $GITHUB_ENV
|
||||
echo "Это не продакшн тег, latest не добавляем"
|
||||
fi
|
||||
else
|
||||
echo "DOCKER_TAG=latest" >> $GITHUB_ENV
|
||||
echo "IS_PRODUCTION_TAG=false" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: steps.resolve_tag.outputs.has_tag == 'true'
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
|
||||
# Сначала собираем базовый образ с runtime
|
||||
- name: Build base image
|
||||
if: steps.resolve_tag.outputs.has_tag == 'true'
|
||||
run: |
|
||||
docker build --target runtime -t dicoop/mono-base:${{ env.DOCKER_TAG }} .
|
||||
docker push dicoop/mono-base:${{ env.DOCKER_TAG }}
|
||||
|
||||
|
||||
# Если это продакшн тег, добавляем latest
|
||||
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
|
||||
docker tag dicoop/mono-base:${{ env.DOCKER_TAG }} dicoop/mono-base:latest
|
||||
docker push dicoop/mono-base:latest
|
||||
fi
|
||||
|
||||
|
||||
# Создаем сервисные образы на основе базового
|
||||
- name: Build desktop image
|
||||
if: steps.resolve_tag.outputs.has_tag == 'true'
|
||||
run: |
|
||||
echo "FROM dicoop/mono-base:${{ env.DOCKER_TAG }}" > Dockerfile.desktop
|
||||
echo "CMD [\"pnpm\", \"-F\", \"@coopenomics/desktop\", \"run\", \"start\"]" >> Dockerfile.desktop
|
||||
docker build -t dicoop/desktop:${{ env.DOCKER_TAG }} -f Dockerfile.desktop .
|
||||
docker push dicoop/desktop:${{ env.DOCKER_TAG }}
|
||||
|
||||
|
||||
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
|
||||
docker tag dicoop/desktop:${{ env.DOCKER_TAG }} dicoop/desktop:latest
|
||||
docker push dicoop/desktop:latest
|
||||
fi
|
||||
|
||||
|
||||
- name: Build controller image
|
||||
if: steps.resolve_tag.outputs.has_tag == 'true'
|
||||
run: |
|
||||
echo "FROM dicoop/mono-base:${{ env.DOCKER_TAG }}" > Dockerfile.coopback
|
||||
echo "CMD [\"pnpm\", \"-F\", \"@coopenomics/controller\", \"run\", \"start\"]" >> Dockerfile.coopback
|
||||
docker build -t dicoop/coopback:${{ env.DOCKER_TAG }} -f Dockerfile.coopback .
|
||||
docker push dicoop/coopback:${{ env.DOCKER_TAG }}
|
||||
|
||||
|
||||
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
|
||||
docker tag dicoop/coopback:${{ env.DOCKER_TAG }} dicoop/coopback:latest
|
||||
docker push dicoop/coopback:latest
|
||||
fi
|
||||
|
||||
|
||||
# TEMP: парсер используется существующим деплоем как dicoop/cooparser,
|
||||
# пока не мигрировали на dicoop/parser из отдельного coopenomics/parser repo.
|
||||
# Оставляем сборку и пуш образа до завершения миграции потребителей.
|
||||
- name: Build parser image
|
||||
if: steps.resolve_tag.outputs.has_tag == 'true'
|
||||
run: |
|
||||
echo "FROM dicoop/mono-base:${{ env.DOCKER_TAG }}" > Dockerfile.cooparser
|
||||
echo "CMD [\"pnpm\", \"-F\", \"@coopenomics/parser\", \"run\", \"start\"]" >> Dockerfile.cooparser
|
||||
@@ -104,39 +137,44 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Build notificator image
|
||||
if: steps.resolve_tag.outputs.has_tag == 'true'
|
||||
run: |
|
||||
echo "FROM dicoop/mono-base:${{ env.DOCKER_TAG }}" > Dockerfile.notificator
|
||||
echo "CMD [\"pnpm\", \"-F\", \"coop-notificator\", \"run\", \"start\"]" >> Dockerfile.notificator
|
||||
docker build -t dicoop/notificator:${{ env.DOCKER_TAG }} -f Dockerfile.notificator .
|
||||
docker push dicoop/notificator:${{ env.DOCKER_TAG }}
|
||||
|
||||
|
||||
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
|
||||
docker tag dicoop/notificator:${{ env.DOCKER_TAG }} dicoop/notificator:latest
|
||||
docker push dicoop/notificator:latest
|
||||
fi
|
||||
|
||||
|
||||
- name: Build notifications image
|
||||
if: steps.resolve_tag.outputs.has_tag == 'true'
|
||||
run: |
|
||||
echo "FROM dicoop/mono-base:${{ env.DOCKER_TAG }}" > Dockerfile.notifications
|
||||
echo "CMD [\"pnpm\", \"-F\", \"@coopenomics/notifications\", \"run\", \"sync\"]" >> Dockerfile.notifications
|
||||
docker build -t dicoop/notifications:${{ env.DOCKER_TAG }} -f Dockerfile.notifications .
|
||||
docker push dicoop/notifications:${{ env.DOCKER_TAG }}
|
||||
|
||||
|
||||
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
|
||||
docker tag dicoop/notifications:${{ env.DOCKER_TAG }} dicoop/notifications:latest
|
||||
docker push dicoop/notifications:latest
|
||||
fi
|
||||
|
||||
# Отправка хука для деплоя
|
||||
|
||||
# Отправка хука для деплоя. К этому моменту build-contracts.yaml
|
||||
# уже завершился успешно (мы и есть его workflow_run-зависимый
|
||||
# потребитель), значит образ `dicoop/contracts:<branch>` гарантированно
|
||||
# обновлён в DockerHub — гонки 2026-05-13 больше не воспроизвести.
|
||||
- name: Trigger deployment webhook
|
||||
if: ${{ success() }}
|
||||
if: success() && steps.resolve_tag.outputs.has_tag == 'true'
|
||||
run: |
|
||||
if [[ $GITHUB_REF == refs/tags/*alpha* ]]; then
|
||||
if [[ "${{ env.DOCKER_TAG }}" == *alpha* ]]; then
|
||||
# Хук для тестнета (alpha теги)
|
||||
curl -X POST ${{ vars.TESTNET_WEBHOOK_URL }} \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '${{ env.DOCKER_TAG }}'
|
||||
elif [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
else
|
||||
# Хук для продакшена (остальные теги)
|
||||
curl -X POST ${{ vars.PRODUCTION_WEBHOOK_URL }} \
|
||||
-H 'Content-Type: application/json' \
|
||||
@@ -145,22 +183,23 @@ jobs:
|
||||
|
||||
# Уведомление в Telegram об успехе
|
||||
- name: Telegram notify success
|
||||
if: ${{ success() }}
|
||||
if: success() && steps.resolve_tag.outputs.has_tag == 'true'
|
||||
run: |
|
||||
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
|
||||
ADDITIONAL_INFO=" (с тегом latest)"
|
||||
else
|
||||
ADDITIONAL_INFO=""
|
||||
fi
|
||||
|
||||
|
||||
curl -s -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage \
|
||||
-d chat_id=${{ secrets.TELEGRAM_CHAT_ID }} \
|
||||
-d text="✅ [GITHUB MONO] Успешная сборка контейнеров: $GITHUB_REPOSITORY ($GITHUB_REF) [${{ env.DOCKER_TAG }}]$ADDITIONAL_INFO"
|
||||
-d text="✅ [GITHUB MONO] Успешная сборка контейнеров: $GITHUB_REPOSITORY (sha=${{ github.event.workflow_run.head_sha }}) [${{ env.DOCKER_TAG }}]$ADDITIONAL_INFO"
|
||||
|
||||
# Уведомление в Telegram об ошибке
|
||||
# Уведомление в Telegram об ошибке (только если до этого был
|
||||
# резолвен тэг — иначе no-op запуск без сборки и без алертов).
|
||||
- name: Telegram notify failure
|
||||
if: ${{ failure() }}
|
||||
if: failure() && steps.resolve_tag.outputs.has_tag == 'true'
|
||||
run: |
|
||||
curl -s -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage \
|
||||
-d chat_id=${{ secrets.TELEGRAM_CHAT_ID }} \
|
||||
-d text="❌ [GITHUB MONO] Ошибка при сборке контейнеров: $GITHUB_REPOSITORY ($GITHUB_REF) [${{ env.DOCKER_TAG }}]"
|
||||
-d text="❌ [GITHUB MONO] Ошибка при сборке контейнеров: $GITHUB_REPOSITORY (sha=${{ github.event.workflow_run.head_sha }}) [${{ env.DOCKER_TAG }}]"
|
||||
@@ -31,6 +31,15 @@ on:
|
||||
- 'components/contracts/build-all.sh'
|
||||
- 'components/contracts/docker/**'
|
||||
- '.github/workflows/build-contracts.yaml'
|
||||
# Дополнительно ловим все push'и тэгов: build-containers.yaml зависит от
|
||||
# успеха этой сборки через workflow_run, и без unconditional запуска по
|
||||
# тегу build-containers не получит триггер для коммитов, где .cpp не
|
||||
# менялся (например `chore(release): publish` правит только версии
|
||||
# пакетов). См. историю гонки 2026-05-13: webhook деплоя сработал на
|
||||
# 2:34 быстрее обновления `dicoop/contracts:testnet`, на чейн уехал
|
||||
# старый wasm.
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
Reference in New Issue
Block a user