Pull to refresh

Отправка уведомлений на почту при деплое проекта

Reading time 4 min
Views 5.8K
В нашем проекте при каждом деплое разработчики, тестеры и ещё пара людей получают замечательные письма:
Subject: Наш проект версии v1.1.1 обновлён на сервере 'testing'

user1 выложил следующие обновления на сервер 'testing':

Коммиты по задачам:
jira.local/browse/PROJECT-1234

Полный список коммитов с предыдущего обновления:
4392a53 Thu Aug 18 17:50:32 2011 +0700 user1 / [PROJECT-1234] сделал полезное
f2fcfe2 Thu Aug 18 17:37:53 2011 +0700 user1 / сделал страшное
cb1fcbe Wed Aug 17 15:18:10 2011 +0700 user2 / зарефакторил

Изменения по файлам:
file1 | 4 ++--
file2 | 8 ++++----
file3 | 8 ++++----
3 files changed, 10 insertions(+), 10 deletions(-)

Такое решение помогло нам избавиться от вопросов тестеров «Ну что, выложили уже исправление бага XXX?», «Что нового на тестовом сервере?». Так же — все члены команды, отдел внедрения и руководство в курсе, что происходит с кодом на серверах.
Для работы используется git, capistrano (+ multistage), php, bash (+ некоторые консольные утилитки). Если интересно — заходим под кат.

Алгоритм работы

  • Обновляем код на сервере testing (cap testing deploy)
  • После deploy:restart срабатывает хук, создающий тег в репозитории. Тег формируется на основе версии проекта (хранится в конфиге, в репозитории), названии staging-сервера и названии релиза
  • В репозитории срабатывает хук. Если пришёл не тег — игнорируем, если же тег:
    • Распиливаем его на компоненты: версия, сервер
    • Определяем предыдущий тег на этом же сервере
    • Если тега нет — значит эта первая установка и генерировать различия не стоит, там может быть несколько тысяч коммитов
    • Если же есть тег — генерируем список различий, выдёргиваем из них список задач; составляем список изменённых файлов
    • Отправка сгенерированного письма

Создание тега

Про настройку capistrano и capistrano-multistage уже где только не написано, поэтому я только расскажу, как у нас добавляется тег.

Будем считать, что у нас в корне репозитория есть файл configs.ini, который содержит ключ runtime.version. За основу был взят gist#381852.

namespace :deploy do

...

  after "deploy:restart", "deploy:git:push_deploy_tag"
  namespace :git do
    desc "Place release tag into Git and push it to server."
    task :push_deploy_tag do
      user = `git config --get user.name`.strip
      email = `git config --get user.email`.strip
      version = `git cat-file -p #{real_revision}:configs.ini | fgrep runtime.version | awk -F '[ =]+' '{print $2}'`.strip
      puts `git tag v#{version}-#{stage}-#{release_name} #{real_revision} -m "Deployed by #{user} <#{email}>"`
      puts `git push --tags`
    end
  end
end

Что здесь происходит:
  • Извлекаем данные текущего пользователя (имя и почту) из конфига гита
  • Берём файл configs.ini из устанавливаемой ревизии и выдёргиваем версию
  • Создаём аннотированный тэг. В аннотации указываем, кто и когда задеплоил
  • Публикуем теги

Обрабатываем обновление репозитория

Хуку pre-receive на вход (stdin) подётся 3 значения: предыдущая и текущая ревизии, refname.
Читаем входящие параметры и убеждаемся, что пришёл тег:
while read oldrev newrev refname
do
    rev_type=$(git cat-file -t $newrev 2>/dev/null)

    case "$refname","$rev_type" in
        refs/tags/*,tag)
...
        ;;
    esac
done

Выделяем название тега, разбиваем его на части, ищем предыдущий тег для этого сервера:
tag=${refname##refs/tags/}

version=`echo $tag | cut -d- -f1`
server=`echo $tag | cut -d- -f2`
prevtag=$(git describe --tags --abbrev=0 --match="*-$server-*" $newrev^ 2>/dev/null)

Если в $prevtag пусто — значит это первая установка на сервер. Если версия у нового и старого тега совпадает — это обновление, если же нет — установка новой версии. Таким образом мы генерируем корректный заголовок письма.

Начнём формировать тело письма. Сперва — определим кто осмелился задеплоить:
eval $(git for-each-ref --shell --format='
          tagger=%(taggername)
          tagged=%(taggerdate)' $refname
        )
echo "$tagger выложил следующие обновления на сервер '$server':" > msg

Теперь разберём коммиты по задачам. Последние в Jira именуются по маске <алиас проекта>-<id задачи>, все разработчики в обязательном порядке указывают алиас задачи (uppercase) в коммите. Если задача крупнее чем на 30 минут и требует более 1 коммита — создаётся ветка, по алиасу задачи, и тогда в коммитах эту самую задачу мы уже не упоминаем. Итого, чтобы достать список задач нам нужно выполнить не сложную обработку регуляркой:
git log $rev_range --abbrev-commit --pretty="format:%s" > tmpfile
php >tickets <<END
<?php
\$f = file_get_contents("$tmp");
if (preg_match_all("#([A-Z.]+-\d+)#", \$f, \$matches)) {
    \$matches[1] = array_unique(\$matches[1]);
    foreach (\$matches[1] as \$match) {
        echo '$JIRA_HOST/browse/', \$match, PHP_EOL;
    }
}
END

Если в итоге файл tmpfile не пуст — добавляем его к телу письма.
Дальше идёт информация, которая интересует только разработчиков проекта: списки коммитов и изменённых файлов:
echo "Полный список коммитов с предыдущего обновления:" >> msg
git log $rev_range --no-merges --abbrev-commit --pretty="format:%h %ad %an / %s" >> msg

echo -e "\n\nИзменения по файлам:" >>msg
git diff --stat=140,110 $rev_range >>msg

Ну и, наконец, тупая отправка письма:
cat msg | mail -s "$subject" $MAIL_TO


Все файлы можно взять на гитхабе: github.com/zvirusz/git-deploy-notify

P.S. Если кто-нибудь поможет переписать кусок кода, выдёргивающий имена задач, на perl/bash — я буду очень рад.
Tags:
Hubs:
+4
Comments 11
Comments Comments 11

Articles