Привет! Меня зовут Иван, и я автор проекта «Код на салфетке» — небольшой команды, в которой мы совмещаем написание обучающих статей, коммерческую разработку и open source.
Сегодня расскажу об одном из наших инструментов, который родился из боли всей команды: как мгновенно узнавать о проблемах в CI/CD, не заглядывая в почту и не обновляя вкладку репозитория.
Предпосылки
В конце прошлого года в нашей команде был разработчик, который упорно игнорировал запуск линтеров и тестов перед пушем. Он просто отправлял изменения и уходил в закат, пока через несколько часов мы (или он сам, часов через пять) не замечали, что пуш завалился, и нужно править.
Это доставляло массу неудобств:
Приходилось чаще проверять статусы CI/CD;
Возникала лишняя ругань.
Так родилась идея найти «оповещатор» о статусе воркфлоу прямо в Telegram-чат. Через некоторое время после появления идеи — когда без такого инструмента уже нельзя было обходиться — появился Actions Telegram Notifier
. Наш собственный Action для GitHub/Gitea Actions.
Поиск решения
Когда возникла потребность, я сразу начал искать готовые решения. В GitHub Actions Marketplace их действительно много. Но, внимательно изучив все варианты - выяснил, что ни одно из них полностью не соответствует нашим требованиям.
Я выделил такие требования к готовому решению:
Поддержка тем в супергруппах;
Информативность;
Кастомизируемость.
Сравнение с "конкурентами":
Поддержка тем в супергруппах. В наших проектах мы используем Telegram-супергруппы, в которых создаём темы под разные проекты. Почти все готовые решения поддерживают отправку уведомлений только в обычные чаты (или личку).
Пример организации супергруппы в Telegram:
Информативность. Оповещения должны быть информативными, но не перегруженными. Большинство готовых решений предлагают одну строку текста с описанием события — и всё.
Пример встроенного в GitLab оповещения:
Пример оповещения нашего Action:
Кастомизируемость. Большая часть решений не позволяет управлять содержанием уведомления: ни убрать/добавить информацию, ни вставить свой текст. На скриншоте выше, например, строка
"job: pre-commit linters"
— это как раз пример пользовательского текста. При желании, можно вообще оставить только поле со статусом и кнопкой на коммит.
Кандидатов нет - пишем своё!
В итоге я оказался в классической ситуации: «ничего из готового не покрывает хотелки». Оставался один путь — писать свой Action.
Как писать свой экшен? Нужно создать специальный файл action.yml
, в котором описываются параметры, способ запуска и прочие детали экшена.

На чём писать экшен? Тут тоже всё просто — на чём угодно! Можно на Python, JS или любом другом языке — главное, чтобы код запускался в среде раннера. Я выбрал Rust. На тот момент это было моё первое знакомство с языком, и без сложностей не обошлось.
Как запускать экшен? Если взглянуть на блок runs
на скрине выше, есть два варианта:
Непосредственно в среде раннера. Обычно так запускаются экшены на JS, которым достаточно Node.js, уже установленного в среде.
С помощью Docker-образа. Это заранее собранный образ, указанный в репозитории экшена. Я выбрал этот вариант, поскольку Rust — компилируемый язык.
Разработка экшена
Разработка началась с изучения матчасти, а именно:
Как работают экшены?
Как их писать?
Какие данные о воркфлоу доступны внутри раннера?
С чем едят Rust?
И десятки других вопросов.
Часть из них я уже затронул в предыдущем блоке, но самый важный — про данные внутри раннера. Нужно было чётко понимать, что именно мне доступно для работы.
Поскольку используемая нами Gitea почти полностью совместима с GitHub, начал изучение с документации GitHub Actions. К счастью, у GitHub в этом плане всё отлично — документация подробная и понятная.
Первая версия
Первая версия была написана "на коленке" — быстро и без лишних заморочек.
Кому интересно, вот последний коммит той самой первой версии: ссылка на коммит
Принцип работы я подсмотрел у "конкурентов": посмотрел, как они это делают, и собрал свою реализацию. Как и большинство, я получал данные из переменных окружения, доступных внутри раннера.
Логика была простая:
Получаем данные из окружения и складываем в словарь;
Формируем текст сообщения;
Отправляем его в Telegram.
Весь экшен умещался примерно в 215 строк кода в одном main.rs
.
В первой версии, в тексте оповещения я выделил следующие поля:
Workflow status
- статус выполнения воркфлоу, получаемый из{{ job.status }}
;Actor
: инициатор запуска воркфлоу;Repository
: название репозитория в котором запустился воркфлоу;Branch
: название ветки в которой запустился воркфлоу;Commit Message
: сокрашённое до первой строки сообщение коммита;Message
иFooter
: дополнительные текстовые поля.
Скриншот оповещения первой версии:

Минусы первой версии
Любое отклонение в структуре данных могло «сломать» отправку. Всё потому, что я завязался не на реальные данные раннера, а на переменные окружения — а они зависят от типа события. В итоге экшен нормально работал только при событии push
.
Второй минус — неподдерживаемость. Двести строк кода в main.rs
было тяжело расширить или адаптировать под другие события.
Ну и если говорить честно — это был далеко не лучший код. Сделаем скидку: это было моё первое знакомство с Rust =)
Вторая версия экшена
Спустя некоторое время, уже после того как я немного разобрался в Rust и написал ReBack (и статью про него), появилась новая потребность — добавить уведомления о Pull Request.
Собравшись с мыслями, я решил полностью переписать экшен.
Ссылка на актуальный репозиторий
При переработке проекта я учёл все прошлые недочёты и начал с самого важного — архитектуры. Теперь всё разбито на логические модули. Вместо словаря с данными используются структуры и энумы.

Часть параметров по-прежнему берётся из переменных окружения — например, токен бота или дополнительный текст. Но основные данные я теперь получаю из файла /github/workflow/event.json
. В нём удобно и структурированно лежит вся необходимая информация. Более того, там есть данные, которых нет в стандартных переменных окружения.

Это открывает большие возможности — теперь при необходимости можно легко:
добавить новые поля в уведомления;
обрабатывать новые события.
Основной алгоритм работы остался прежним — собираем данные, формируем сообщение, отправляем в Telegram.
Во второй версии текст оповещения тоже претерпел некоторые изменения:
В поле
Actor
теперь ссылка на инициатора;В поле
Repository
также теперь ссылка на репозиторий;Для события
Pull Request
создаётся кнопка со ссылкой на PR;Для события
Pull Request
отображается полеPR Title
с заголовком PR'а и ссылкой на него.
Добавить ссылки позволило чтение данных из файла event.json
.
Пример оповещения о пуше:

Пример оповещения о Pull Request:

Сборка образа экшена
Напомню: Rust — это компилируемый язык и с этим связанна одна особенность...
При запуске экшена в воркфлоу он начинал компилироваться прямо на раннере. Это тратит время и ресурсы впустую.
Чтобы решить эту проблему, я настроил сборку Docker-образа прямо в репозитории проекта.

Теперь экшен использует уже собранный образ, а не компилируется каждый раз при запуске. Всё стало быстрее и стабильнее.
Вклад от сообщества
Неожиданно, ещё в первой версии кто-то открыл issue
. Это был пользователь Kastov, который предложил улучшить текст уведомления. После того как я внёс это изменение, он прислал ещё и полноценный Pull Request с рядом улучшений.
А уже после выхода второй версии обратился ZAlexanderV. Он столкнулся с тем, что экшен поддерживал только события Push
и Pull Request
, а ему нужно было ещё и Workflow Dispatch
.
Сложность добавления Workflow Dispatch
заключалась в его кастомной природе. Я не могу заранее предусмотреть все возможные поля, которые туда передаются, — каждый проект настраивает это по-своему. Пришлось искать компромисс между универсальностью и поддержкой пользовательских параметров.
Пример оповещения на событие Workflow Dispatch
:

Меня удивляло то, что этим проектом пользуются другие люди и когда я видел открытый issue
, честно, даже немного нервничал. Мне было приятно, когда Kastov внёс некоторые изменения и интересно добавлять возможности для ZAlexanderV. Наверное, ради таких эмоций и стоит заниматься опенсорсом.
Пример использования
Использовать экшен очень просто — достаточно добавить в существующий workflow следующий шаг:
- name: Run Telegram Notify Action
uses: proDreams/actions-telegram-notifier@main
with:
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
chat_id: ${{ secrets.TELEGRAM_CHAT_ID }}
status: ${{ job.status }}
notify_fields: "actor,repository,workflow,repo,commit"
message: "Docker image successfully built and pushed to registry."
footer: "Workflow completed"
Обязательно добавьте в secrets
два поля:
TELEGRAM_BOT_TOKEN
— токен Telegram-ботаTELEGRAM_CHAT_ID
— ID чата, куда будут приходить уведомления
Поле status
оставляем как есть — оно нужно, чтобы в уведомлении отображался актуальный статус выполнения джоба.
Остальные параметры — опциональные. Вы можете кастомизировать их под свои задачи.
Подробное описание всех доступных полей — в README.md репозитория.
Что дало это команде?
У меня нет каких-то конкретных цифр, которые обычно пишут в таких постах — типа “Продуктивность выросла на 80%!!!”.
Но могу точно сказать по ощущениям: мы стали быстрее реагировать на проблемы со сборкой или тестами. Раньше нужно было открывать репозиторий и следить за процессом вручную — это было неудобно и отнимало время.
Теперь всё иначе: сделал задачу, запушил изменения — и можно идти дальше. Когда воркфлоу завершится (или упадёт), в Telegram сразу приходит уведомление.
То же самое с Pull Request’ами: не нужно больше писать “я там PR открыл” — достаточно просто создать его, и все увидят уведомление в чате.
Заключение
Этот проект оказался очень полезным для нашей команды, и, судя по количеству запусков (а их уже больше 2000!), он пригодился не только нам.
С его помощью я:
глубже разобрался в механике GitHub/Gitea Actions;
прокачал навыки в Rust, особенно в работе с архитектурой и обработкой внешних данных;
на практике увидел, как важны хорошие уведомления для быстрой реакции и прозрачности рабочих процессов;
и просто получил удовольствие от того, что можно сделать что-то маленькое — но реально полезное.
Буду рад, если экшн окажется полезен и в вашем проекте. А если захотите предложить улучшения, добавить поддержку других событий или просто что-то обсудить — пишите issue!
Обсудим, подумаем, возможно — добавим!
Подписывайтесь на наш Telegram‑канал «Код на салфетке», у нас много интересного как для новичков, так и профессионалов!