Я покажу вам, насколько легко может быть автоматизация процесса выпуска и увеличения версии.
Я увлекаюсь автоматизацией процессов и твердо верю, что вложение времени и денег в автоматизацию процессов на этапе разработки - это первостепенная необходимость, независимо от того, как долго продлится проект. Инвестируя в автоматизацию процессов, вы значительно сокращаете дальнейшие расходы на техническое обслуживание.
Я думаю, что одна из тем, которая не освещена должным образом в статьях по ML, - это автоматизация процесса выпуска и увеличение версии проекта. Я не хочу обсуждать возможные варианты выпуска в этом посте, это не об этом. Здесь мы обсудим, как настроить процесс выпуска для вашего инструмента CI / CD (конвейеры Bitbucket, конвейеры Gitlab и т. Д.). Я думаю, можно с уверенностью сказать, что в большинстве случаев выпуск - это файл или набор файлов, которые будут каким-то образом интегрирован в существующее клиентское решение (независимо от того, является ли это jar-файл с приложением загрузки Spring внутри, набор скриптов python, которые используются для обучения модели и прогнозов, или образ докера, в котором выполняется основное приложение). Формулировка проблемы: мы хотим управлять версиями продукта и иметь возможность вернуться к конкретной версии, чтобы иметь возможность тестировать и диагностировать проблемы, возникшие в производственной среде.
Управление версиями
Для начала давайте определим, что означает число в строке версии.

Определение из семантического управления версиями (https://semver.org/)
Учитывая номер версии MAJOR.MINOR.PATCH-pre_release-label, увеличьте:
- ОСНОВНАЯ версия при внесении несовместимых изменений API
- Версия MINOR при добавлении функций обратно совместимым способом.
- Версия PATCH при исправлении ошибок с обратной совместимостью.
Pre-release-label - это метка-заменитель версии, которая сохраняется перед выпуском.
Это оптимальное определение, но, конечно, вы можете выбрать свою собственную схему управления версиями. Итак, согласно этому определению, нам нужно определить три задачи:
- Выпустить и увеличить основную версию
- Выпустить и увеличить дополнительную версию
- Выпустить и увеличить версию патча
Следующим шагом является определение того, как выпуск будет представлен в истории GIT, за исключением тега git. Для этого существует несколько методов, наиболее популярными из которых являются отдельная ветвь выпуска или отдельная фиксация. В этом посте мы будем обсуждать отдельный коммит, потому что его гораздо проще автоматизировать (слияние не требуется).
Отдельная фиксация
Начнем с теории. Отдельная фиксация - это фиксация, которая не принадлежит ни к какой ветке и может быть указана только напрямую. Как сделать его доступным для поиска в истории git, не зная хеша коммита? Вам просто нужно присвоить ему тег git. Затем вы можете фильтровать историю git по тегам в любом пользовательском интерфейсе VCS.
VERSION файл
Как-то вам нужно сохранить актуальную строку версии. Самый простой способ - сохранить его в текстовом файле в корне репозитория. Политики файла VERSION:
- Содержимое файла нельзя изменить вручную
- Его нельзя изменить в функциональной ветви (лучше всего создать этап конвейера, который сравнивает его с мастером)
- Файл изменяется только после выпуска и автоматически фиксируется непосредственно в основной ветке.
Настройка конвейеров CI / CD
В файл конвейеров CI / CD вы должны добавить три настраиваемых / ручных шага / задания, которые будут выполнять выпуск и соответствующее приращение. И, при желании, добавьте шаг / задание, которое выполняется только в ветвях функций и проверяет, был ли файл VERSION изменен по сравнению с мастером.
Сценарий Bash, чтобы проверить, изменился ли файл VERSION относительно master:
git diff --exit-code VERSION
А теперь самое интересное:
- Нам нужно создать скрипт, который управляет файлом VERSION с функциями: получить версию, получить версию с заполнителем, увеличить версию major / minor / patch
- Мы должны определить политику ограничения ветки выпуска (выпуск может быть выполнен только в основной ветке) и создать отдельный поток отправки фиксации.
- Определите шаг выпуска CI / CD (здесь мы определим его для конвейеров Bitbucket и конвейеров Gitlab)
Ненавижу писать сценарии на bash. Я использую bash только для определения высокоуровневых шагов и запуска из него сценариев python, потому что я твердо убежден, что сценарии bash имеют уродливую нотацию, не читаются менее опытными пользователями Linux и не обслуживаются в долгосрочной перспективе. fire библиотека может помочь вам легко написать хорошие сценарии Python, запускаемые через CLI. Посмотрите мой предыдущий пост:
VERSION скрипт управления файлами (обсуждать нечего, только операции записи файлов):
#!/usr/bin/env python
import os
import re
import fire
pre_release_placeholder = 'SNAPSHOT'
version_filepath = os.path.join('.', 'VERSION')
version_pattern = re.compile(fr'^\d+.\d+.\d+(-{pre_release_placeholder})?$')
def get(with_pre_release_placeholder: bool = False):
with open(version_filepath, 'r') as version_file:
version_lines = version_file.readlines()
assert len(version_lines) == 1, 'Version file is malformed'
version = version_lines[0]
assert version_pattern.match(version), 'Version string is malformed'
if with_pre_release_placeholder:
return version
else:
return version.replace(f'-{pre_release_placeholder}', '')
def write_version_file(major: int, minor: int, patch: int):
version = f'{major}.{minor}.{patch}-{pre_release_placeholder}'
with open(version_filepath, 'w') as version_file:
version_file.write(version)
def inc_patch():
version = get()
major, minor, patch = version.split('.')
write_version_file(major, minor, int(patch) + 1)
def inc_minor():
version = get()
major, minor, patch = version.split('.')
write_version_file(major, int(minor) + 1, patch)
def inc_major():
version = get()
major, minor, patch = version.split('.')
write_version_file(int(major) + 1, minor, patch)
if __name__ == "__main__":
fire.Fire({
'get': get,
'inc-patch': inc_patch,
'inc-minor': inc_minor,
'inc-major': inc_major
})
Теперь займемся разработкой сценария выпуска. Во-первых, мы должны проверить, запущен ли сценарий выпуска в правой ветке:
# Bitbucket
commit=${BITBUCKET_COMMIT:-$(git rev-parse HEAD)}
# Gitlab
commit=${CI_COMMIT_SHA:-$(git rev-parse HEAD)}
# Define ALLOWED_RELEASE_BRANCH in VCS pipelines secret variables.
branch=${ALLOWED_RELEASE_BRANCH:-master}
if ! git branch -a --contains "${commit}" | grep -e "^[* ]*remotes/origin/${branch}\$"
then
echo -e "###\n### Not on ${branch}. Only ${branch} commits can be released.\n###"
exit 1
else
echo -e "###\n### Releasing of ${commit} on ${branch}\n###"
fi
Затем вам нужно написать кое-что о выпуске, например: опубликовать образ докера или скопировать его в корзину S3 или что-то еще, что вам нужно. Не забудьте сделать это с правильным файлом ВЕРСИИ:
version=$(./scripts/version.py get)
version_file=VERSION
echo ${version} > ${version_file}
Затем мы должны отправить отсоединенный коммит с измененным на предыдущем шаге файлом VERSION:
# Define some cool release pusher to ditinguish from amnual commits :) git config user.name "Elon Musk" git config user.email "[email protected]" echo "Pushing detached tag of new version" git add ${version_file} git commit -m "Release version ${version}" git tag -a ${version} -m "Release version ${version} tag" git push origin ${version}
Вернитесь в основную ветку и увеличьте версию с предварительным заполнителем:
echo "Pushing new version to ${branch}"
git fetch origin "${branch}:${branch}" || git pull
git checkout "${branch}"
# Parameter of the script
release_type=${1}
./scripts/version.py inc-${release_type}
next_working_version=$(./scripts/version.py get --with-pre-release-placeholder)
git add ${version_file}
git commit -m "Incrementing working version to ${next_working_version} after ${version} release."
git push origin ${branch}
Вот и все. Мы создали чистый скрипт выпуска. Теперь давайте разработаем фрагменты шагов конвейера Bitbucket и Gitlab.
Bitbucket:
image:
name: python:3.7.6-slim-buster
pipelines:
default:
- step:
- name: Test version file not changed
- script:
- git diff --exit-code VERSION
custom:
release-inc-patch:
- step:
caches:
- pip
name: Release current version and increment patch version
script:
- apt-get update && apt-get install -y git
- pip install fire==0.2.1
- ./scripts/release.sh patch
release-inc-minor:
- step:
caches:
- pip
name: Release current version and increment minor version
script:
- apt-get update && apt-get install -y git
- pip install fire==0.2.1
- ./scripts/release.sh minor
release-inc-major:
- step:
caches:
- pip
name: Release current version and increment major version
script:
- apt-get update && apt-get install -y git
- pip install fire==0.2.1
- ./scripts/release.sh major
Выполнение настраиваемых шагов конвейера: https://confluence.atlassian.com/bitbucket/run-pipelines-manually-861242583.html
Gitlab:
test-version-changed:
stage: test
name: Test version file not changed
script:
- git diff --exit-code VERSION
release-inc-patch:
stage: deploy
image: python:3.7.6-slim-buster
caches:
- pip
name: Release current version and increment patch version
script:
- apt-get update && apt-get install -y git
- pip install fire==0.2.1
- ./scripts/release.sh patch
when: manual
only:
- master
release-inc-minor:
stage: deploy
image: python:3.7.6-slim-buster
caches:
- pip
name: Release current version and increment minor version
script:
- apt-get update && apt-get install -y git
- pip install fire==0.2.1
- ./scripts/release.sh minor
when: manual
only:
- master
release-inc-major:
stage: deploy
image: python:3.7.6-slim-buster
caches:
- pip
name: Release current version and increment major version
script:
- apt-get update && apt-get install -y git
- pip install fire==0.2.1
- ./scripts/release.sh major
when: manual
only:
- master
Запускайте ручные задания: https://forum.gitlab.com/t/gitlab-ci-run-pipeline-manually/13797
Вот и все! Все исходники вы можете найти на моем GitHub: https://github.com/mbalatsko/release-version-increment
Надеюсь, эта статья будет полезной и информативной. Жду ваших отзывов!