Автоматическое управление номером версии c помощью Azure DevOps

от автора

В этой статье я расскажу, как мы организовали последовательное автоматическое увеличение номера версии приложения при выполнении коммита в ветку main с помощью Azure DevOps Pipeline.

Мы делаем этого для того, чтобы все пользователи и разработчики могли видеть, какая именно версия продукта развернута в той или иной среде.

Задача

В зависимости от архитектуры приложения номер версии может храниться в разных местах. Здесь я приведу пример приложения .NET Core, где номер версии находится в теге AssemblyVersion XML-файла проекта с расширением .csproj. По умолчанию этого тега в проекте нет, инициировать его добавление можно, установив в свойствах проекта его значение, например, 1.0.0.1.

В «классических» приложениях .NET Framework номер версии хранится в файле Properties\AssemblyInfo.cs. Этот уже не XML-файл. Способ поиска и редактирования нужного тега будет несколько отличаться.

При работе с git-репозиторием мы используем trunk-подход, описанный здесь: trunkbaseddevelopment.com. В рамках этого подхода, в отличие от GitFlow, разработчики создают ветки с коротким жизненным циклом от основной ветки main. Эту методологию, в частности, продвигает и Microsoft, мы используем слегка упрощенный подход по сравнению с их Release Flow, описанным в этой статье.

В нашем случае в ветку main код попадает в результате пул-реквестов, но без контрольной автоматической сборки. Поэтому мы запускаем CI-сборку сразу после коммита в мастер. Нам требуется, чтобы, в случае успешного завершения этой сборки, увеличивалась бы последняя цифра в номере версии приложения в AssemblyVersion. Например, было 1.3.4.15, стало 1.3.4.16.

Решение

Сценарий пайплайна в общих чертах выглядит так:

  1. Получить содержимое ветки main.

  2. Обновить номер версии с помощью PowerShell-скрипта. Для этого на агенте необходимо создать новую ветку. Мы будем делать все изменения в этой ветке, использовать ее для сборки и потом будет объединять ее с веткой main. Эта ветка останется локальной, она не будет отправляться в серверный репозиторий. В конце процесса мы ее удалим.

  3. Выполнить сборку.

  4. Если сборка прошла успешно, обновить номер версии в репозитории также с помощью PowerShell-скрипта.

Стоит упомянуть несколько важных моментов.

  • С момента получения содержимого ветки main и до окончания сборки в ветку main могут попасть новые коммиты. По этой причине Azure-агент получает не содержимое ветки, а конкретный коммит. После получения кода репозиторий становится со «сдвинутой головой» (HEAD detached). Именно поэтому и надо обновлять номер версии в отдельной ветке.

  • Azure-агент, который будет обновлять репозиторий, должен в нем дополнительно авторизоваться. Для этого понадобится на уровне пайплайна сохранить персональный ключ доступа (PAT) одного из авторизованных пользователей. Это значение является секретным и должно соответствующим образом сохраняться. Оно будет использоваться при отправке коммита через HTTPS.

  • Чтобы не «возбуждать» CI-сборку при попадании нового номера версии в main, при выполнении коммита необходимо в комментарии указать [skip ci].

Реализация

Мы создаем CI-сборку, которая должна запускаться автоматически после того, как код попадает в ветку main. Поэтому скрипт пайплайна начинается с фразы:

trigger: - main

Далее следует стандартный блок выбора агента, установки базовых переменных и обновления nuget-пакетов:

Pool:   vmImage: 'windows-latest'  variables:   solution: '**/*.sln'   buildPlatform: 'Any CPU'   buildConfiguration: 'Release'  steps: - task: NuGetToolInstaller@1 - task: NuGetCommand@2   inputs:     restoreSolution: '$(solution)'

Теперь, когда агент получил из репозитория свежее содержимое ветки main, выполняем PoweShell-скрипт.

- task: PowerShell@2   displayName: 'Assembly Version Generation'   # первая часть – увеличение номера версии   # если сборка падает, новый номер версии не сохраняется в репозитории   inputs:     targetType: 'inline'     script: |

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

Опция —quiet при вызове git указывается, чтобы пайплайн не воспринимал служебные сообщения от git как сообщения об ошибках и не останавливал процесс.

          # имя XML-файла проекта, в который будут вноситься изменения           $ProjectFile = '.\azure-devops-versioning\azure-devops-versioning.csproj'              # создаем новую ветку           git branch DevOps/test_$(Build.BuildNumber) –quiet                   # переключаемся в новую ветку           git checkout DevOps/test_$(Build.BuildNumber) –quiet                # ищем строку, содержащую тег <AssemblyVersion>           foreach($line in Get-Content -Path $ProjectFile)            {$AssemblyVersionLine = $line | Select-String -Pattern '<AssemblyVersion>' -CaseSensitive            if ($AssemblyVersionLine) {break}            }            # ищем номер версии в строке через операцию -match           $AssemblyVersionLine -match ('(<AssemblyVersion>)(.+)(</AssemblyVersion>)$')               # получаем строку версии           $Version = $Matches[2]           #  разбиваем строку на массив           $VerParts = $Version.split('.')           # получаем последнюю часть массива и увеличиваем на 1           $VerParts[3] = ([int]$VerParts[3] + 1)           # собираем обратно в строку через точку           $NewVersion = $VerParts -join '.'           # получаем новую строку с тегом <AssemblyVersion> и новым номером версии           $NewAssemblyVersionLine = $AssemblyVersionLine -replace $Version, $NewVersion                # заменяем старую строку с тегом <AssemblyVersion> на новую           (Get-Content $ProjectFile) | Foreach-Object { $_ -replace $AssemblyVersionLine, $NewAssemblyVersionLine } | Set-Content $ProjectFile                    # вводим информацию о пользователе-агенте           git config user.email "azure.agent@profinfotech.ru"           git config user.name "Azure Agent"           # индексируем измененный файл           git add $ProjectFile           # фиксируем изменения в локальном репозитории с комментарием           git commit -m "[skip ci] Pipeline Modification: AssemblyVersion = $NewVersion"         

В этом скрипте мы создали новую локальную ветку с использованием номера билда, который в Azure DevOps является уникальным. То есть, при каждой CI-сборке на агенте будет создаваться новая локальная ветка. Мы переключились на эту новую ветку, нашли нужный файл и заменили в нем AssemblyVersion, увеличив последний номер на 1. Мы сделали коммит в локальный репозиторий и запускаем сборку с помощью стандартного блока:

- task: VSBuild@1   inputs:     solution: '$(solution)'     msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:DesktopBuildPackageLocation="$(build.artifactStagingDirectory)\WebApp.zip" /p:DeployIisAppPath="Default Web Site"'     platform: '$(buildPlatform)'     configuration: '$(buildConfiguration)'

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

Если же сборка завершилась успешно, мы запускаем PowerShell-скрипт.

- task: PowerShell@2   displayName: 'Send new version to main'       # вторая часть – после успешной сборки новая версия отправляется в серверный репозиторий   inputs:     targetType: 'inline'     script: |

Этот скрипт переключит нас в main, получит обновление main, объединит нашу ветку с веткой main, удалит нашу ветку и сделает push в центральный репозиторий, авторизовавшись с помощью PAT-токена:

          # переключаемся на ветку main           git checkout main –quiet           # обновляем локальную ветку main           git pull –quiet            # объединяем нашу ветку с веткой main, в комментарии указываем номер версии           git merge DevOps/test_$(Build.BuildNumber) -m "[skip ci] Pipeline Modification: AssemblyVersion = $NewVersion" –quiet                     # удаляем локальную ветку           git branch -d DevOps/test_$(Build.BuildNumber)            # отправляем локальную ветку main в серверный репозиторий           # PAToken – это переменная пайплайна, содержащая значение персонального ключа           git push https://kanailov:$(PAToken)@github.com/Kanailov/azure-devops-versioning.git main –quiet

В приведенном примере git-репозиторий находится в GitHub. В том случае, если используется git-репозиторий, встроенный в Azure DevOps, формат HTTPS-запроса немного отличается:

git push https://Personal%20Access%20Token:$(PAToken)@<URL_git-репозитория> main --quiet

Ссылка

Пример проекта выложен на Github. В репозитории находится yml-скрипт azure-pipelines.yml.


ссылка на оригинал статьи https://habr.com/ru/articles/576464/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *