Как стать автором
Обновить

Gitlab за решеткой: синхронизация репозиториев за VPN средствами bash

Время на прочтение6 мин
Количество просмотров7.3K

Пролог

Закусывая гудроном остатки радиоактивной водки, с интересом наблюдаю за всеобщей истерией. Добралась она и до нас, когда ключевой заказчик решил завернуть свои системы за VPN, в том числе и Gitlab, где мы должны хранить исходники и обновлять их перед релизами. Делать это вручную утомительно, поэтому решил автоматизировать процесс, а заодно написать данную статью, по сути – рабочую инструкцию для коллег, ну и для вас, уважаемые читатели.

Статья предназначена для тех, кто столкнулся с проблемой, описанной ниже, и подразумевает базовые навыки владения Gitlab, git и bash, чтобы при необходимости подправить скрипты под себя.

Ссылка на исходники

Проблема

Есть наш Gitlab, где хранятся исходники проекта, и целевой Gitlab, находящийся в закрытом за VPN контуре. Нужно автоматизировать процесс обновления исходников конкретной production-ветки. При этом есть сервер, имеющий доступ до обоих серверов, позволяющий решить проблему.

Целевой сервер Gitlab не дает настроить стандартное зеркалирование, равно как и обратное зеркалирование, являющееся premium-фичей, поэтому обновлять будем командами git: pull и push. Получается такая нехитрая схема:

Подготовительные работы

Для доступа к репозиториям нужна либо служебная учетка, либо Access Token, о последнем расскажу подробнее. Фактически, он выполняет те же функции, что и обычная учетка, ниже будет приведен пример его использования.

Для создания токена для проекта нужно зайти в настройки проекта (Settings ⇨ Access Tokens), заполнить имя токена, выбрать роль и отметить права:

Для исходного репозитория нам достаточно только чтения, т.е. read_repository, а для целевого, соответственно, write_repository. В момент создания токена вы получите его пароль, который нужно сохранить, т.к. восстановить его не получится. Аналогичным образом создается персональный токен в профиле пользователя.

Также есть токен для групп, но управлять им через интерфейс можно только с версии Gitlab 14.7, до этого создавать/отзывать его можно только через командую строку администратора Gitlab.

Токены могут использоваться в командах git как обычные пользователи, например, так:

git clone "https://{token_name}:{token_password}@git.company.ru/path/to/project.git/"

Синхронизатор

Ссылка на исходники есть в начале поста, здесь оставлю ссылку непосредственно на скрипт.

Алгоритм

Это теория, как работает скрипт, к прочтению не обязательно.

Перед началом цикла синхронизации нужно выполнить подготовку локального репозитория на сервере, а именно клонировать исходный репозиторий (с origin=source) и добавить целевой репозиторий (с origin=target). Источники сохранят токены, и в дальнейшем они не понадобятся.

Алгоритм синхронизации следующий:

  1. Проверяем наличие изменений в исходном репозитории: git remote

  2. Если изменения были, получаем их: git pull

  3. Получаем лог изменения (автор и заголовок коммита) в локальные переменные: git log

  4. Оправляем ветку в целевой репозиторий: git push

  5. Опционально: отправляем уведомление в Telegram

Разбор скрипта

Нет ничего хуже, чем скрипт на bash, скачанный из интернета, особенно если пишешь на этом языке так же часто, как происходят полные солнечные затмения. Поэтому я решил его частично разобрать, полезно для тех, кто захочет подправить скрипт под свои нужды. Также отмечу, что не явлюсь специалистом по git и bash, возможно, задачу можно было решить несколько проще.

Блок настройки

Обязателен для заполнения:

# реквизиты исходного Gitlab
SOURCE_REPO=""
SOURCE_ACCESS_USER=""
SOURCE_ACCESS_TOKEN=""
SOURCE_ORIGIN="source"

# реквизиты целевого Gitlab
TARGET_REPO=""
TARGET_ACCESS_USER=""
TARGET_ACCESS_TOKEN=""
TARGET_ORIGIN="target"

# обновляемая ветка
BRANCH=""

# название проекта и путь к папке с репозиторием
PROJECT="Project Name"
REPO_PATH="$(pwd)/repo"

В SOURCE_REPO и TARGET_REPO нужно указать путь до проектов в Gitlab. Например, если адрес проекта https://git.company.ru/path/to/project, нужно взять все, что после https:// и добавить в конце .git/, чтобы в консоль не сыпались предупреждения, т.е. значение переменной будет "git.company.ru/path/to/project.git/"

Переменная PROJECT используется для формирования заголовка сообщения в Telegram, об этом ниже.

REPO_PATH – адрес локального репозитория, в данном случае берем папку repo в текущем расположении. Репозиторий будет создан единожды по команде, об этом беспокоиться не стоит.

Блок начальной установки репозитория

# установка репозитория
setup() {
    echo "Установка репозитория"
    echo "Путь: $REPO_PATH"
    if [[ -d $REPO_PATH ]]; then
        echo "Каталог уже существует, установка не требуется"
    else
        echo "Каталог $REPO_PATH не существует"
        git clone -b $BRANCH -o $SOURCE_ORIGIN "https://$SOURCE_ACCESS_USER:$SOURCE_ACCESS_TOKEN@$SOURCE_REPO" $REPO_PATH
        pushd $REPO_PATH
        git remote add $TARGET_ORIGIN "https://$TARGET_ACCESS_USER:$TARGET_ACCESS_TOKEN@$TARGET_REPO"
        popd
    fi
}

Это единственный блок, где будут нужны токены, в дальнейшем они сохранятся в настройках источников (origin). Команды pushd и popd нужны для временного перехода по другому пути и возврата в предыдущий соответственно. Их использование связано с тем, что команды git нужно выполнять в папке репозитория.

Для вызова этого блока и начальной настройки репозитория вызовем скрипт с аргументом -s:

# выполняем первоначальную настройку локального репозитория
sh mirror -s

Блок получения изменений и обновления

# загрузить исходный репозиторий
update() {
    echo "Проверка исходного репозитория..."
    git remote update $SOURCE_ORIGIN && git status -uno | grep -q 'Your branch is behind' && changed=1
    if [[ $changed = 1 ]]; then
        echo "Получаем изменения"
        git pull --ff $SOURCE_ORIGIN
        git log -1 --oneline
        IFS="|"; read commit_author commit_subject <<< "$(git log -1 --pretty=format:"%an|%s")"
        return 0
    else
        echo "Загрузка не требуется"
        return 1
    fi
}

# обновить целевой репозиторий
push() {
    echo "Обновляем целевой репозиторий..."
    git push $TARGET_ORIGIN
}

Самой загадочной строчкой является git remote… Сам толком не понимаю, как она работает, честно стырено из интернета. Так или иначе, здесь проверяется наличие изменений по логу git status. Там будет строка вида "Your branch is up to date…" или "Your branch is behind…", по ней и определяем наличие изменений.

git log выводит статус в консоль последний коммит, а затем его же для разбора в переменные commit_author и commit_subject, чтобы затем бот передал их в сообщении. Если оповещение ботом не нужно, лог можно выпилить.

После первоначальной установки репозитория push не вызывается, т.к. это может быть не нужно, но его можно вызвать принудительно с аргументом -p. Далее вызываем скрипт без аргументов, что запускает процесс проверки наличия изменения и push при их наличии.

# только push (после sh mirror -s)
sh mirror -p

# синхронизация репозиториев
sh mirror

Блок отправки оповещения в telegram

# отправить уведомление в Telegram
sendMessage() {
    echo "Отправляем уведомление"
    sh telegram -H "<b>$PROJECT</b>"$'\n'"Ветка $BRANCH была обновлена, автор $commit_author"$'\n\n'"<i>$commit_subject</i>"
}

Здесь используется другой скрипт: https://github.com/fabianonline/telegram.sh

Он давно не обновлялся, но успешно работает, в целом кроме bash и curl ничего не требует. Для того чтобы синхронизатор отправлял уведомления, нужно создать бота и положить скрипт telegram рядом с синхронизатором, указав в настройках токен бота и идентификатор чата(-ов), куда будет отправляться уведомление. Если хотите, можете добавить флаг -N, чтобы не было звукового уведомления от сообщения в чате.

Итоговое сообщение выглядит так:

Рассуждения о ботах Telegram выходят за рамки статьи, отмечу только, что получить идентификатор чата удалось тремя последовательными действиями:

  1. Вызов скрипта sh telegram -l

  2. Отправка любого сообщения в нужный чат с ботом

  3. Повторный вызов sh telegram -l, получаем json и ищем там нужный chat.id

Автоматизация

Скрипт есть, осталось настроить его запуск. Предлагаю два варианта.

Вызов по cron

Применил его в своих проектах, пока не появится время на реализацию второго варианта. Все просто: добавляем вызов скрипта каждые N минут по cron. Будут идти лишние запросы, но ничего страшного. Найти информацию по теме можно в интернете, могу порекомендовать описание crontab и этот генератор расписаний.

Для совсем ленивых. Настройка вызова скрипта из папки /home/{myusername}/mirror каждые 5 минут:

*/5 * * * * cd ~/mirror; sh mirror
  1. Вызываем редактор расписания crontab -e

  2. С помощью vim вводим наше расписание.

  3. Ломаем клавиатуру о колено в попытке выйти из vim

  4. :wq

  5. Profit!

Вызов по webhook

В Gitlab есть инструмент Webhooks (Settings ⇨ Webhooks), где можно указать адрес вызова при событии, например, push в нужную ветку. В нашем случае достаточно принять запрос и запустить скрипт.

К сожалению, для этого варианта bash уже недостаточно, нужно использовать другие средства, как минимум nginx или self-hosted сервис, поэтому конкретной реализации не будет. Впрочем, если статья наберет достаточное количество лайков, просмотров и комментариев, выпущу новый ролик… Ой, простите, я же не видеоблогер =)

Тем не менее, если интерес будет, обязательно прикручу хуки в своем проекте и расскажу подробнее в отдельной статье.

Шпаргалка

  1. Скачиваем скрипт синхронизатора.

  2. Скачиваем скрипт Telegram, если хотим оповещения, кладем рядом.

  3. Заполняем нужные переменные в скриптах.

  4. Выполняем первоначальную установку репозитория: sh mirror -s

  5. Настраиваем автоматический вызов удобным способом: sh mirror

Заключение

Если понравилась статья, почитайте другие:

Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 1: ↑1 и ↓0+1
Комментарии13

Публикации

Истории

Работа

DevOps инженер
43 вакансии

Ближайшие события