ci: атомарный release.yaml, убрать workflow_run-связку (#366)
build-contracts + build-containers через workflow_run упёрлись в:
(а) default-branch caveat (новая логика не активна, пока не в main),
(б) `${{ github.event.workflow_run.head_sha }}` иногда пуст в YAML-
выражениях — описание см. в шаге Resolve tag, инцидент v2026.5.14
не дёрнул PRODUCTION_WEBHOOK_URL.
Замена — один `release.yaml` на push тэга `v*`: резолвит ветку через
`git branch --contains`, собирает контракты → пушит
`dicoop/contracts:<branch>`, собирает базу + сервисные образы →
пушит `dicoop/<svc>:<tag>`, шлёт webhook. Гонок нет by construction.
`build-contracts.yaml` остаётся только на push веток для CI-
обновления `dicoop/contracts:dev|testnet|main` без релизного тэга.
Триггер `tags: ['*']` и логика резолва ветки через --contains
оттуда удалены — это теперь забота release.yaml.
Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,223 +0,0 @@
|
||||
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:
|
||||
workflow_run:
|
||||
workflows: ["Build contracts container"]
|
||||
types: [completed]
|
||||
# Ручной fallback: запустить сборку контейнеров и деплой для конкретного
|
||||
# тэга, если автоматический workflow_run не сработал (типичный кейс —
|
||||
# миграционное окно: на default-ветке (main) старая версия build-containers
|
||||
# с `push: tags`, а на dev/testnet уже новая с `workflow_run`. До мержа
|
||||
# в main новая логика не активна).
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Git-тэг для сборки и деплоя (например v2026.5.13-alpha-3)'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
# Job запускается, если: (а) workflow_run от build-contracts успешен,
|
||||
# либо (б) ручной запуск через workflow_dispatch.
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
- name: Checkout repository at triggering SHA
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.event.workflow_run.head_sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Resolve tag for triggering commit
|
||||
id: resolve_tag
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
TAG_NAME="${{ github.event.inputs.tag }}"
|
||||
HEAD_SHA=$(git rev-parse "$TAG_NAME^{commit}")
|
||||
echo "Ручной запуск: tag=$TAG_NAME sha=$HEAD_SHA"
|
||||
else
|
||||
HEAD_SHA="${{ env.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
|
||||
fi
|
||||
echo "Найден тэг: $TAG_NAME"
|
||||
echo "has_tag=true" >> $GITHUB_OUTPUT
|
||||
echo "DOCKER_TAG=$TAG_NAME" >> $GITHUB_ENV
|
||||
echo "HEAD_SHA=$HEAD_SHA" >> $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 "Триггер: ${{ github.event_name }}"
|
||||
echo "Триггерящий коммит: ${{ env.HEAD_SHA }}"
|
||||
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: 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
|
||||
docker build -t dicoop/cooparser:${{ env.DOCKER_TAG }} -f Dockerfile.cooparser .
|
||||
docker push dicoop/cooparser:${{ env.DOCKER_TAG }}
|
||||
|
||||
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
|
||||
docker tag dicoop/cooparser:${{ env.DOCKER_TAG }} dicoop/cooparser:latest
|
||||
docker push dicoop/cooparser:latest
|
||||
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() && steps.resolve_tag.outputs.has_tag == 'true'
|
||||
run: |
|
||||
if [[ "${{ env.DOCKER_TAG }}" == *alpha* ]]; then
|
||||
# Хук для тестнета (alpha теги)
|
||||
curl -X POST ${{ vars.TESTNET_WEBHOOK_URL }} \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '${{ env.DOCKER_TAG }}'
|
||||
else
|
||||
# Хук для продакшена (остальные теги)
|
||||
curl -X POST ${{ vars.PRODUCTION_WEBHOOK_URL }} \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '${{ env.DOCKER_TAG }}'
|
||||
fi
|
||||
|
||||
# Уведомление в Telegram об успехе
|
||||
- name: Telegram notify 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 (sha=${{ env.HEAD_SHA }}) [${{ env.DOCKER_TAG }}]$ADDITIONAL_INFO"
|
||||
|
||||
# Уведомление в Telegram об ошибке (только если до этого был
|
||||
# резолвен тэг — иначе no-op запуск без сборки и без алертов).
|
||||
- name: Telegram notify 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 (sha=${{ env.HEAD_SHA }}) [${{ env.DOCKER_TAG }}]"
|
||||
@@ -7,12 +7,18 @@ name: Build contracts container
|
||||
# ke-bootstrap'ом (отдельный артефакт), который при старте ноды деплоит
|
||||
# контракты через `cleos set contract`.
|
||||
#
|
||||
# Триггер — push в одну из веток-окружений (dev / testnet / main).
|
||||
# Маппинг:
|
||||
# Триггер — push в одну из веток-окружений (dev / testnet / main) с
|
||||
# изменением .cpp/CMakeLists/build-скриптов. Маппинг:
|
||||
# dev → IS_TESTNET=ON, tag=dev
|
||||
# testnet → IS_TESTNET=ON, tag=testnet
|
||||
# main → IS_TESTNET=OFF, tag=main + latest
|
||||
#
|
||||
# ВАЖНО: тэги `v*` тут НЕ обрабатываются — они уезжают в `release.yaml`,
|
||||
# который делает релиз атомарно (контракты + контейнеры + webhook в
|
||||
# одном job'е). См. release.yaml для контекста — там в комментарии
|
||||
# описана причина (гонка build-contracts и build-containers по push'у
|
||||
# тэга, инцидент 2026-05-13 с ledger2 walletop sweep'ом).
|
||||
#
|
||||
# Сам compile идёт через CDT-образ `dicoop/blockchain_v5.1.1:dev`
|
||||
# (тот же, что использует `components/contracts/build.sh|build-all.sh`).
|
||||
# Артефакты копируются в slim alpine-образ.
|
||||
@@ -31,15 +37,6 @@ 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:
|
||||
@@ -48,55 +45,30 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Полная история нужна, чтобы при push'е тэга резолвить ветку
|
||||
# через `git branch -r --contains $SHA` (см. следующий step).
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Determine build mode and docker tag
|
||||
# Источник режима — ВЕТКА, не имя тэга. Сначала пытаемся взять
|
||||
# `github.ref_name` как ветку (push веток); если не подошло — это
|
||||
# push тэга, и мы ищем какая из dev/testnet/main содержит коммит.
|
||||
# Тэг такого окружения нести семантику не должен (например,
|
||||
# `v2026.5.13-alpha-2` → ветка testnet, BUILD_MODE=test).
|
||||
run: |
|
||||
REF="${{ github.ref_name }}"
|
||||
REF_TYPE="${{ github.ref_type }}"
|
||||
BRANCH=""
|
||||
if [ "$REF_TYPE" = "branch" ]; then
|
||||
BRANCH="$REF"
|
||||
else
|
||||
SHA="${{ github.sha }}"
|
||||
for candidate in main testnet dev; do
|
||||
if git branch -r --contains "$SHA" 2>/dev/null | grep -qE "^[[:space:]]*origin/${candidate}$"; then
|
||||
BRANCH="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
case "$BRANCH" in
|
||||
case "${{ github.ref_name }}" in
|
||||
main)
|
||||
echo "BUILD_MODE=prod" >> $GITHUB_ENV
|
||||
echo "DOCKER_TAG=main" >> $GITHUB_ENV
|
||||
echo "EXTRA_TAG=latest" >> $GITHUB_ENV
|
||||
echo "BUILD_MODE=prod" >> "$GITHUB_ENV"
|
||||
echo "DOCKER_TAG=main" >> "$GITHUB_ENV"
|
||||
echo "EXTRA_TAG=latest" >> "$GITHUB_ENV"
|
||||
;;
|
||||
testnet)
|
||||
echo "BUILD_MODE=test" >> $GITHUB_ENV
|
||||
echo "DOCKER_TAG=testnet" >> $GITHUB_ENV
|
||||
echo "EXTRA_TAG=" >> $GITHUB_ENV
|
||||
echo "BUILD_MODE=test" >> "$GITHUB_ENV"
|
||||
echo "DOCKER_TAG=testnet" >> "$GITHUB_ENV"
|
||||
echo "EXTRA_TAG=" >> "$GITHUB_ENV"
|
||||
;;
|
||||
dev)
|
||||
echo "BUILD_MODE=test" >> $GITHUB_ENV
|
||||
echo "DOCKER_TAG=dev" >> $GITHUB_ENV
|
||||
echo "EXTRA_TAG=" >> $GITHUB_ENV
|
||||
echo "BUILD_MODE=test" >> "$GITHUB_ENV"
|
||||
echo "DOCKER_TAG=dev" >> "$GITHUB_ENV"
|
||||
echo "EXTRA_TAG=" >> "$GITHUB_ENV"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported ref: ref_name=$REF ref_type=$REF_TYPE resolved_branch='$BRANCH'" >&2
|
||||
echo "Unsupported branch ${{ github.ref_name }}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
echo "Resolved BRANCH=$BRANCH (ref_name=$REF ref_type=$REF_TYPE)"
|
||||
|
||||
- name: Pull CDT toolchain image
|
||||
run: docker pull dicoop/blockchain_v5.1.1:dev
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
name: Release
|
||||
|
||||
# Атомарный релизный workflow. Триггерится исключительно push'ем тэга
|
||||
# `v*`. В одном job'е по порядку:
|
||||
# 1) собирает контракты под mode (prod/test) и пушит `dicoop/contracts:<branch>`,
|
||||
# 2) собирает базовый образ + сервисные контейнеры и пушит `dicoop/<svc>:<tag>`,
|
||||
# 3) шлёт webhook деплоя (testnet/production по типу тэга).
|
||||
#
|
||||
# Зачем атомарно: раньше `build-contracts` и `build-containers`
|
||||
# крутились параллельно по push'у тэга, и webhook от build-containers
|
||||
# (~8 мин) обгонял окончание build-contracts (~11 мин) → ансибл на
|
||||
# тестнете подхватывал ПРЕДЫДУЩИЙ образ контрактов и перетирал чейн
|
||||
# старым wasm. Попытка связать их через workflow_run упёрлась в
|
||||
# default-branch caveat и потерю head_sha в YAML-выражениях. Теперь
|
||||
# нет двух workflow'ов — нет гонки, нет fallback'ов.
|
||||
#
|
||||
# Триггер по веткам (dev/testnet/main без тэга) тут НЕ обрабатывается —
|
||||
# для CI-обновления `dicoop/contracts:<branch>` без релиза остаётся
|
||||
# `build-contracts.yaml`.
|
||||
#
|
||||
# Резолв ветки. Тэг семантики окружения не несёт (например
|
||||
# `v2026.5.13-alpha-2` может лежать на testnet или dev — определяет
|
||||
# именно ветка, на которую сделан merge перед тэгом). Поэтому ищем
|
||||
# первое совпадение SHA с remote-ветками main/testnet/dev.
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Resolve branch, build mode and tags
|
||||
run: |
|
||||
TAG_NAME="${{ github.ref_name }}"
|
||||
SHA="${{ github.sha }}"
|
||||
BRANCH=""
|
||||
for candidate in main testnet dev; do
|
||||
if git branch -r --contains "$SHA" 2>/dev/null | grep -qE "^[[:space:]]*origin/${candidate}$"; then
|
||||
BRANCH="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -z "$BRANCH" ]; then
|
||||
echo "::error::Тэг $TAG_NAME ($SHA) не лежит ни на одной из dev/testnet/main"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$BRANCH" in
|
||||
main)
|
||||
BUILD_MODE=prod
|
||||
CONTRACTS_TAG=main
|
||||
CONTRACTS_EXTRA=latest
|
||||
;;
|
||||
testnet)
|
||||
BUILD_MODE=test
|
||||
CONTRACTS_TAG=testnet
|
||||
CONTRACTS_EXTRA=
|
||||
;;
|
||||
dev)
|
||||
BUILD_MODE=test
|
||||
CONTRACTS_TAG=dev
|
||||
CONTRACTS_EXTRA=
|
||||
;;
|
||||
esac
|
||||
|
||||
# Прод-тэги (без -alpha/-beta/-rc/-test) уезжают на боевой
|
||||
# webhook + получают тэг :latest у сервисных образов.
|
||||
if [[ "$TAG_NAME" =~ -(alpha|beta|rc|test) ]]; then
|
||||
IS_PROD=false
|
||||
WEBHOOK_URL='${{ vars.TESTNET_WEBHOOK_URL }}'
|
||||
else
|
||||
IS_PROD=true
|
||||
WEBHOOK_URL='${{ vars.PRODUCTION_WEBHOOK_URL }}'
|
||||
fi
|
||||
|
||||
{
|
||||
echo "TAG_NAME=$TAG_NAME"
|
||||
echo "SHA=$SHA"
|
||||
echo "BRANCH=$BRANCH"
|
||||
echo "BUILD_MODE=$BUILD_MODE"
|
||||
echo "CONTRACTS_TAG=$CONTRACTS_TAG"
|
||||
echo "CONTRACTS_EXTRA=$CONTRACTS_EXTRA"
|
||||
echo "IS_PROD=$IS_PROD"
|
||||
echo "WEBHOOK_URL=$WEBHOOK_URL"
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
echo "Release $TAG_NAME → branch=$BRANCH, build_mode=$BUILD_MODE, contracts:$CONTRACTS_TAG, prod=$IS_PROD"
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
# === Этап 1: контракты ===
|
||||
|
||||
- name: Pull CDT toolchain image
|
||||
run: docker pull dicoop/blockchain_v5.1.1:dev
|
||||
|
||||
- name: Compile contracts
|
||||
working-directory: components/contracts
|
||||
run: |
|
||||
./build-all.sh "$BUILD_MODE"
|
||||
echo "--- build/contracts ---"
|
||||
ls -la build/contracts/
|
||||
|
||||
- name: Stage contracts docker context
|
||||
working-directory: components/contracts
|
||||
run: |
|
||||
ROOT="docker/.context"
|
||||
rm -rf "$ROOT"
|
||||
mkdir -p "$ROOT/contracts"
|
||||
|
||||
# Имена контрактов = аргументы add_contract_build(...) только в
|
||||
# незакомменченных строках (CMake-комментарий начинается с `#`).
|
||||
NAMES=$(grep -E '^[[:space:]]*add_contract_build\(' CMakeLists.txt \
|
||||
| sed -E 's/^[[:space:]]*add_contract_build\(([^)]*)\).*/\1/')
|
||||
echo "Contracts to package:"
|
||||
echo "$NAMES"
|
||||
|
||||
MANIFEST="$ROOT/contracts/manifest.json"
|
||||
|
||||
pack_one() {
|
||||
local NAME="$1" WASM="$2" ABI="$3"
|
||||
if [ ! -f "$WASM" ] || [ ! -f "$ABI" ]; then
|
||||
echo "::warning::Skipping $NAME: artifacts missing ($WASM / $ABI)" >&2
|
||||
return 1
|
||||
fi
|
||||
mkdir -p "$ROOT/contracts/$NAME"
|
||||
cp "$WASM" "$ROOT/contracts/$NAME/$NAME.wasm"
|
||||
cp "$ABI" "$ROOT/contracts/$NAME/$NAME.abi"
|
||||
local SHA_WASM=$(sha256sum "$WASM" | awk '{print $1}')
|
||||
local SHA_ABI=$(sha256sum "$ABI" | awk '{print $1}')
|
||||
printf ' {"name":"%s","sha256_wasm":"%s","sha256_abi":"%s"}' \
|
||||
"$NAME" "$SHA_WASM" "$SHA_ABI"
|
||||
}
|
||||
|
||||
{
|
||||
printf '{\n'
|
||||
printf ' "build_mode": "%s",\n' "$BUILD_MODE"
|
||||
printf ' "git_sha": "%s",\n' "$SHA"
|
||||
printf ' "tag": "%s",\n' "$TAG_NAME"
|
||||
printf ' "branch": "%s",\n' "$BRANCH"
|
||||
printf ' "built_at": "%s",\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
printf ' "contracts": [\n'
|
||||
|
||||
FIRST=1
|
||||
emit() {
|
||||
entry=$(pack_one "$@") || return 0
|
||||
[ $FIRST -eq 0 ] && printf ',\n'
|
||||
FIRST=0
|
||||
printf '%s' "$entry"
|
||||
}
|
||||
|
||||
for c in $NAMES; do
|
||||
if [ "$c" = "system" ]; then
|
||||
# У system многоконтрактная сборка: разносим под-контракты
|
||||
# eosio.* как полноценные элементы каталога, сам "system"
|
||||
# как имя пропускаем.
|
||||
for sub in build/contracts/system/contracts/eosio.*/; do
|
||||
[ -d "$sub" ] || continue
|
||||
sub_name=$(basename "$sub")
|
||||
emit "$sub_name" "$sub/$sub_name.wasm" "$sub/$sub_name.abi"
|
||||
done
|
||||
else
|
||||
emit "$c" "build/contracts/$c/$c.wasm" "build/contracts/$c/$c.abi"
|
||||
fi
|
||||
done
|
||||
|
||||
printf '\n ]\n'
|
||||
printf '}\n'
|
||||
} > "$MANIFEST"
|
||||
|
||||
cp docker/Dockerfile "$ROOT/Dockerfile"
|
||||
cp docker/entrypoint.sh "$ROOT/entrypoint.sh"
|
||||
chmod +x "$ROOT/entrypoint.sh"
|
||||
|
||||
echo "--- manifest.json ---"
|
||||
cat "$MANIFEST"
|
||||
|
||||
- name: Build and push contracts image
|
||||
working-directory: components/contracts
|
||||
run: |
|
||||
IMAGE="dicoop/contracts"
|
||||
SHORT_SHA="${SHA::7}"
|
||||
docker build \
|
||||
--label "org.opencontainers.image.revision=$SHA" \
|
||||
--label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||
--label "build.mode=$BUILD_MODE" \
|
||||
-t "$IMAGE:$CONTRACTS_TAG" \
|
||||
./docker/.context
|
||||
docker push "$IMAGE:$CONTRACTS_TAG"
|
||||
|
||||
docker tag "$IMAGE:$CONTRACTS_TAG" "$IMAGE:$CONTRACTS_TAG-$SHORT_SHA"
|
||||
docker push "$IMAGE:$CONTRACTS_TAG-$SHORT_SHA"
|
||||
|
||||
if [ -n "$CONTRACTS_EXTRA" ]; then
|
||||
docker tag "$IMAGE:$CONTRACTS_TAG" "$IMAGE:$CONTRACTS_EXTRA"
|
||||
docker push "$IMAGE:$CONTRACTS_EXTRA"
|
||||
fi
|
||||
|
||||
- name: Verify pushed contracts image
|
||||
run: |
|
||||
docker run --rm "dicoop/contracts:$CONTRACTS_TAG" list
|
||||
echo "---"
|
||||
docker run --rm "dicoop/contracts:$CONTRACTS_TAG" sha256
|
||||
|
||||
# === Этап 2: контейнеры приложений ===
|
||||
|
||||
- name: Build and push base image
|
||||
run: |
|
||||
docker build --target runtime -t "dicoop/mono-base:$TAG_NAME" .
|
||||
docker push "dicoop/mono-base:$TAG_NAME"
|
||||
if [ "$IS_PROD" = "true" ]; then
|
||||
docker tag "dicoop/mono-base:$TAG_NAME" dicoop/mono-base:latest
|
||||
docker push dicoop/mono-base:latest
|
||||
fi
|
||||
|
||||
- name: Build and push service images
|
||||
run: |
|
||||
build_service() {
|
||||
local SVC="$1" PKG="$2" CMD="$3"
|
||||
local DOCKERFILE="Dockerfile.$SVC"
|
||||
{
|
||||
echo "FROM dicoop/mono-base:$TAG_NAME"
|
||||
echo "CMD [\"pnpm\", \"-F\", \"$PKG\", \"run\", \"$CMD\"]"
|
||||
} > "$DOCKERFILE"
|
||||
docker build -t "dicoop/$SVC:$TAG_NAME" -f "$DOCKERFILE" .
|
||||
docker push "dicoop/$SVC:$TAG_NAME"
|
||||
if [ "$IS_PROD" = "true" ]; then
|
||||
docker tag "dicoop/$SVC:$TAG_NAME" "dicoop/$SVC:latest"
|
||||
docker push "dicoop/$SVC:latest"
|
||||
fi
|
||||
}
|
||||
|
||||
build_service desktop '@coopenomics/desktop' start
|
||||
build_service coopback '@coopenomics/controller' start
|
||||
# TEMP: cooparser — пока не мигрировали потребителей на dicoop/parser
|
||||
# из отдельного coopenomics/parser repo.
|
||||
build_service cooparser '@coopenomics/parser' start
|
||||
build_service notificator 'coop-notificator' start
|
||||
build_service notifications '@coopenomics/notifications' sync
|
||||
|
||||
# === Этап 3: webhook деплоя ===
|
||||
|
||||
- name: Trigger deployment webhook
|
||||
run: |
|
||||
curl -X POST "$WEBHOOK_URL" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "$TAG_NAME"
|
||||
|
||||
- name: Telegram notify success
|
||||
if: success()
|
||||
run: |
|
||||
EXTRA=""
|
||||
if [ "$IS_PROD" = "true" ]; then
|
||||
EXTRA=" (с тегом latest)"
|
||||
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] Релиз $TAG_NAME ($BRANCH, contracts:$CONTRACTS_TAG, sha=${SHA::7})$EXTRA"
|
||||
|
||||
- name: Telegram notify failure
|
||||
if: failure()
|
||||
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] Ошибка релиза $TAG_NAME ($BRANCH, sha=${SHA::7})"
|
||||
Reference in New Issue
Block a user