Недавно на стендапе коллега внес рацпредложение: автоматизировать сборку релизов, взяв за основу готовые уже наработки по взаимодействию с Jira, написанные на Python.
Процесс деплоя у нас следующий: когда накапливается достаточное количество задач, прошедших тестирование из них собирается Релиз-кандидат (RC) в каждом проекте, затронутом задачами, затем задачи тестируются в составе RC. После этого RC заливается на стейджинг сервер, где в близком к боевому окружении все еще раз тестируется и проводится полный регресс. И затем, после необходимых деплойных действий свежий релиз заливается в мастер.
До недавнего времени весь процесс сборки проводился кем-либо из разработчиков вручную. Что отнимало час, два и больше времени и было, мне кажется, не очень интересным занятием. Теперь же, когда уже почти все готово, релиз из 20 задач, затрагивающий 5 проектов, собирается меньше минуты. Остается, конечно еще разрешение конфликтов, запуск пропущенных тестов и прочее, но даже с учетом этого, времени разработчиков и тестировщиков, вынужденных ждать, пока кто-то и первых освободится и создаст RC, экономится немало.
В общем, приступил я к задаче, и она оказалась очень интересной и увлекательной. А что еще надо для удовольствия от работы, как не увлекательных проектов?
Я изучил legacy: оказалось оно напрямую использует API Jira, да и написано, как мне показалось, неоптимально. Например, список задач релиза получался следующим образом: с Jira загружались все существующие релизы, а затем каждый из них по названию сравнивался с названием нашего релиза, пока не найдется нужный:
def get_release_info(config):
try:
release_input = sys.argv[1]
except IndexError:
raise Exception('Enter release name')
releases_json = requests.get(url=RELEASES_LIST_URL, auth=(login, jira_password).json()
for release in releases_json:
if release['name'] == release_input:
...
В общем, прямое взаимодействие с API приводит к не очень читабельному коду. Да и велосипед изобретать не хотелось. Первый же поиск на Github привел меня к JIRA Python Library, достаточно простой и мощной библиотеке. Мердж реквесты я изначально планировал создавать с использованием библиотеки GitPython, также найденной на Github. Но дальнейшее изучение вопроса сразу привело к мысли о поисках чего-то связанного не с git, а скорее сразу с Gitlab. В результате, остановился на самом известном решении: Python GitLab.
Начал с получения списка задач в подходящих для релиза статусах. Решил сильно не переделывать прошлое решение, но сделать его эффективнее, сразу запросив у API задачи нужного релиза:
fix_issues = jira.search_issues(f'fixVersion={release_input}')
fix_id = jira.issue(fix_issues.iterable[0]).fields.fixVersions[0].id
Хотя под капотом там происходит скорее всего то же самое, однако получилось красивее и оптимальнее, но все же не очень читабельно. Далее, из полученных задач, надо собрать ссылки на мердж реквесты. Найденные мердж реквесты решил хранить в namedtuple, они для этого отлично подходят:
Merge_request = namedtuple('Merge_request', ['url', 'iid', 'project', 'issue'])
Мердж реквесты получал также с помощью API Jira:
projects = set()
links_json = requests.get(url=REMOTE_LINK.format(issue_number),
auth=login,jira_password).json()
for link in links_json:
url_parts = link['object']['url'].split('/')
project = f'{url_parts[4]}'
iid = url_parts[6]
projects.add(project)
После этого решил, где только можно использовать найденные библиотеки. Затем, возможно, отрефакторю эти куски.
Далее надо проверить, вдруг нужные ветки RC уже существуют, если уже были попытки сборки, тогда их надо удалить и создать новые. Это я уже делал с помощью библиотеки Python GitLab:
gl = gitlab.Gitlab('https://gitlab...ru/', private_token=GITLAB_PRIVATE_TOKEN)
pr = gl.projects.get(project)
try:
rc = pr.branches.get(f'{RC_name}')
rc.delete()
pr.branches.create({'branch': f'{RC_name}', 'ref': 'master'})
except gitlab.GitlabError:
pr.branches.create({'branch': f'{RC_name}', 'ref': 'master'})
После этого можно приступить к заполнению таблицы в сборочной задаче Jira. Информация в таблице содержится в следующих колонках: №, Задача, Приоритет, Мердж реквесты из задачи в RC, Статус мердж реквеста (прошли ли тесты в Gitlab, есть или нет конфликты, влит/не влит).
На этом шаге столкнулся с неприятной недоработкой Gitlab: если изменения ранее были замержены в ветку-назначение мердж реквеста, то API Gitlab при запросе статуса мердж реквеста дает ответ о наличии конфликтов. В принципе, понять это можно: если вливать нечего, то и влить не получится, но зачем говорить, что в этом месте конфликт? На форумах Gitlab люди уже несколько лет задают вопросы, но ответов пока нет.
В моем случае, это приводит к ложному статусу конфликта и проверке мердж реквеста вручную. Пока ничего не придумал.
Логика при создании мердж реквестов следующая: если не пройдены тесты, либо есть конфликт, мердж реквесты создаются, но в RC не вливаются, в таблице проставляются соответствующие статусы.
Для проверки выполнения тестов ищем список подходящих pipelines. Gitlab их выдает отсортированными по дате в убывающем порядке, прямо как нам надо. Берем первый — он и будет тем, который нужен:
pipelines = project.pipelines.list(ref=f'{issue}')
if pipelines:
pipelines = pipelines[0]
if pipelines.attributes['status'] != 'success':
status = '(x) Тесты не прошли!, '
Затем создаем сами мердж реквесты. Проверяем на наличие открытых, на случай, если это не первая попытка сборки. В случае отсутствия нужного мердж реквеста, создаем такой:
mr = project.mergerequests.list(
state='opened',
source_branch=source_branch, target_branch=target_branch)
if mr:
mr = mr[0]
else:
mr = project.mergerequests.create(
{'source_branch': source_branch,
'target_branch': target_branch,
'title': f"{(MR.issue).replace('-', '_')} -> {RC_name}",
'target_project_id': PROJECTS_NAMES[MR.project],})
status = mr.attributes['merge_status']
url = mr.attributes['web_url']
return status, url, mr
MR.issue.replace('-', '_') — изменение имени задачи, чтобы избавиться от малоинформативных комментариев Gitlaba к задаче в Jira.
Далее смотрим статус полученного мердж реквеста: если нет конфликта, вливаем его в RC. Если конфликт есть, проставляем соответствующие статусы и оставляем для ручной проверки.
Далее по аналогии создаем мердж реквесты из RC в Staging и из Staging в Master для всех проектов. Они будут влиты после проверки всей сборки релиза в RC ветках.
В шаблоне задачи Jira добавили два поля: для преддеплойных и постдеплойных действий. Для всех затронутых задач, алгоритм собирает список таких действий и вносит в сборочную задачу, чтобы ничего не забыть.
Ну и наконец, вывод результата в Jira — создание сборочной задачи:
existing_issue = jira.search_issues(
f'project=PROJ AND summary ~ "Сборка {release_name}"')
if existing_issue:
existing_issue = existing_issue[0]
existing_issue.update(fields={
'description': message,
'customfield_xxx': message_before_deploy,
'customfield_yyy': message_post_deploy,})
else:
issue_dict = {
"fixVersions": [{"name": release_name,}],
'project': {'key': 'PROJ'},
'summary': f"Сборка {release_name}",
'description': message,
'issuetype': {'name': 'RC'}, # специальный тип задачи для сборок
'customfield_xxx': message_before_deploy,
'customfield_yyy': message_post_deploy,}
new_issue = jira.create_issue(fields=issue_dict)
Дальше мысли следующие: запуск скрипта на Gitlab вебхуком от Jira. Можно, например, сделать вебхук на создание сборочной задачи (завести под нее особый тип задачи), либо создание задачи со словом “Сборка” в названии. А Gitlab по этому хуку будет либо запускать скрипт на bash, начинающий весь процесс, либо поднимать Docker образ с Python и запускать скрипт на нем. Хотя второй вариант уже излишне сложен. Да и нужны технические аккаунты в Jira и Gitlab. В общем, окончательного решения пока нет.
Так же можно после создания веток RC развернуть тестовый стенд на нужных ветках и запустить регресс тесты. Дженкинс с этим справится. Но это тоже пока в планах.
Затевалось это все ради ускорения работы тестировщиков и освобождения разработчиков от рутинной работы. Однако экономический смысл тут тоже вполне конкретный: возьмем среднего разработчика (в вакууме) с гипотетической зарплатой 150000 рублей за 8 часов рабочего времени в день. За два года у нас было порядка 700 релизов — это примерно по релизу в день. Некоторые больше, некоторые меньше, но в среднем, я думаю, минимум час времени разработчика на сборку релиза уходил. То есть, автоматизация данного процесса экономит компании минимум 150000/8 = 18750 рублей в месяц.
Попутно для себя сделал отдельный скрипт, показывающий статистику по предстоящему релизу: сколько задач, какие проекты затронуты и т.п. Так как если у меня нет статуса Developer в каком-либо из проектов в Gitlab — будет отказ в доступе при создании мердж реквестов. Также, удобно заранее узнать о деплойных действиях или отловить задачу, попавшую в данный релиз по ошибке. Рассылка уведомлений о релизе была решена с помощью модулей SMTP и email.
Мне решение этой задачи доставило удовольствие: всегда интересно узнать что-то новое и применить это на практике. Будет приятно, если кому-то этот опыт будет полезен.
Автор: Михаил