Git: много хуков полезных и разных

DevOps
Статья является вольным переводом вот этого материала. Любителям длинных и заумных первоисточников можно сразу читать оригинал.

Когда перед нами ставится задача при изменении кодбейса, например, в Github-репозитории выполнить пересборку/перезапуск какого-нибудь приложения на каком-то нашем окружении, то первое, что приходит на ум в качестве возможного триггера такой пересборки, это предоставляемый тем же гитхабом механизм веб-хуков: при наступлении какого-либо события с нашим удаленным репозиторием (т.к. появление нового коммита в какой-нибудь его отслеживаемой ветке) гитхаб задействует соответствующий веб-хук и «дернет» указанный в его настройках сервис, который и запустит процесс пересборки/перезапуска нашего приложения. Это стандартный широкоиспользуемый и простой механизм для таких случаев, все так делают, и все такое…

Но что, если наше приложение живет на хосте, доступ к которому по каким-то причинам гитхабу не положен? Например, хост находится в закрытой сети или за NAT'ом и недоступен из интернета?

В этом случае можно воспользоваться механизмом локальных хуков самого Git, о котором (механизме), как выясняется, мало кто знает даже из тех, кто использует Git уже довольно продолжительное время.

Когда мы создаем локальный репозиторий (инициализируем пустой, клонируем удаленный, ...), то в папке .git, что в его корне, или в самом корне, если это bare-репозиторий, присутствует папка hooks. После инициализации репозитория в этой папке сохраняются шаблоны хуков для различных событий, таких как, например: post-merge, post-receive, post-update и др.

Полное описание поддерживаемых событий для хуков можно найти, например, тут.

Мы воспользуемся этим механизмом и реализуем простенькую push-to-deploy схему для нашего горемычного приложения.

Нам понадобится два локальных репозитория. Создадим их, например, по указанным путям:

1. /opt/repo-dev/example-repo/
2. /opt/repo-remote.git/

Первый репозиторий — это клон нашего удаленного репозитория example-repo на гитхабе.
Второй — это bare репозиторий, копия первого, который будет служить нам исключительно для обработки события post-update при появлении обновлений в удаленном репозитории. Итак, как же мы это реализуем?

Схема очень проста (предположим, мы отслеживаем ветку test, а приложение наше это node.js, управляемый менеджером pm2):

1. Периодически обновляем первый локальный репозиторий до состояния, полностью соотвествующего состоянию удаленного репозитория.
2. Из первого локального репозитория обновляем второй.
3. Как только HEAD у нас переместился — появился новый коммит — во втором репозитории будет задействован хук post-update, который выполняется при появлении любых изменений в репозитории, и который и выполнит необходимые действия по ребилду и рестарту приложения.

Для этого мы делаем следующее:

1. В первом локальном репозитории добавляем remote — второй локальный репозиторий:

cd /opt/repo-dev/example-repo/ && git remote add prod /opt/repo-remote.git

Теперь мы можем выполнять git push из первого локального репозитория во второй.

2. В папке /opt/repo-remote.git/hooks/ создаем файл post-update и делаем его исполняемым:

touch /opt/repo-remote.git/hooks/post-update && chmod +x /opt/repo-remote.git/hooks/post-update

Это обычный шел-скрипт, но согласно внутренней конвенции Git без расширения .sh!
Давайте добавим в него несколько команд:

#!/bin/bash
cd /opt/repo-remote.git
/usr/bin/git --work-tree=/opt/repo/example-repo/ checkout -f origin/test

cd /opt/repo/example-repo/
/usr/bin/npm install
/usr/local/bin/pm2 restart all

Что делает скрипт? Сначала просто выгружает working tree нашего bare репозитория в папку с нашим работающим приложением, а затем пересобирает зависимости и рестартует сервисы pm2. Как видите, никакой магии.

3. Настраиваем cron, который каждые n минут будет обновлять первый репозиторий из удаленного:

git fetch origin && git reset --hard -f origin/test

Т.о. теперь наш хост будет являться регулярным инициатором проверки удаленного репозитория — а не появились ли там обновления?

Прошу заметить, что обновляем локальный репозиторий мы не путем git pull, а путем git reset --hard. Делается это для того, чтобы исключить необходимость мерджей при определенном содержимом очередного коммита — мы делаем локальный репозиторий полной копией удаленного.

4. Сразу после синхронизации первого локального репозитория с удаленным мы делаем пуш всех изменений в наш псевдо-удаленный второй локальный репозиторий:

git push prod test

Вот и все. Как только наш псевдо-удаленный локальный репозиторий получает ненулевые изменения, Git дергает свой хук post-update, который выполняет соответствующий скрипт. И мы получаем простенькую рабочую схему push-to-deploy, которую при желании можно и дальше усовершенствовать в соответствии с нашими потребностями.

«Зачем городить такую неудобоваримую монструозную схему?!» — спросите вы. Я сначала задавался этим же вопросом, но оказалось, что с существующим перечнем хуков Git'а только так мы сможем вызвать необходимую обработку при любом обновлении нашей ветки в удаленном репозитории. Нужный нам хук post-update предназначен для выполнения на remote репозитории (а часть хуков предназначена для выполнения на локальном репозитории). И мы таким вот не очень изящным способом это псевдо-удаленный репозиторий и сэмулировали. Возможно в скором времени появится еще какой-нибудь более удобный хук, выполняющийся локально, и схему можно будет упростить. Но пока так.

В заключение хочу дать несколько советов тем, кто решит это реализовать у себя и, столкнувшись с проблемами неработающего хука или каких-то его частей, будет яростно проклинать меня, Git, автора оригинальной статьи и всех остальных по списку:

1. Помните про специфику работы cron — программы в нем по умолчанию запускаются совсем не в том окружении, которое вы, вероятно, ожидаете.
2. Проверяйте версии ваших утилит (npm, node etc) при вызове их из скриптов и по cron — они могут быть не такими, как при ручном запуске из-за различия путей к исполняемым файлам в переменных окружения, например. А запуск других их версий может приводить к непрогнозируемым результатам.
3. Потратьте 20 минут на просмотр очередной серии Simpsons и возвращайтесь к экспериментам с новыми силами и хорошим настроением

Буду рад любым замечаниям и уточнениям по существу.

Хорошего всем дня!
Tags:gitcontinuous deploymentcontinuous integrationhooks
Hubs: DevOps
+13
16.4k 119
Comments 4

Popular right now

Факультет Java-разработки
January 23, 2021180,000 ₽GeekBrains
SEO-специалист
January 25, 202136,000 ₽GeekBrains
UI-дизайнер
January 25, 202159,900 ₽Нетология
UX/UI дизайнер
January 25, 2021104,900 ₽Нетология

Top of the last 24 hours