Pull to refresh

Применение расширяемых политик Pull Request в VSTS для поддержки процесса разработки

Reading time6 min
Views1.6K

Часто в рамках проверки Pull Request, помимо, собственно, code review, возникает необходимость проделывать набор рутинных проверок. Некоторые проверки могут касаться оформления PR. Другие — проверять смежные условия, которые составляют основу процесса принятия изменений.
Если рутинные проверки не автоматизированы, человек может начать их забывать или обходить. Потому, что рутина — это скучно.


Visual Studio Team Services предлагает довольно удобную инфраструктуру для обработки Pull Request. Сюда входят настраиваемые политики merge builds, назначение ревьюеров, правила слияния принимаемых изменений. Все это дополненной удобной системой обсуждения и комментирования кода.


Мощнейшим инструментом расширения процесса Pull Request являются внешние подключаемые политики.


Об их создании и использовании и поговорим (и посмотрим код)


Расширяемые статусы Pull Request


Статусы PR это флаги, определенные в рамках контекста (context). Контекст — уникальное сочетание имени (name) контекста и "жанра" (genre). Жанр, как правило, один на приложение, манипулирующий статусом.


Например:


Статус 1: genre = 'my-policy', name = 'check-1'
Статус 2: genre = 'my-policy', name = 'check-2'


Статус принимает одно из предустановленных значений. Для целей поддержки процесса нас будут интересовать два: succeeded и failed.


Статусы можно использовать при настройке политики бранча: выбранные флаги должны иметь статус succeeded, для того чтобы PR был принят. Ровно так же реализованны встроенные политики, проверяющие количество аппруверов, наличие прикреплённого тикета, итд.


Переходим в редактирование политики ветки. Добавляем внешнюю политику.
Политика бранча


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


Добавляем внешнюю политику как обязательную


Добавленные статусы будут видны на странице пулл-реквеста в блоке проверок:

Если статус не используется в качестве политики — его значение выводится на странице в разделе Status. Если статус указан в качестве политики — он будет виден в верхней части блока.


Программное управление статусами


Статусами PR можно манипулировать программно, используя REST API. Таким образом, можно реализовать дополнительные проверки PR и транслировать их результат непосредственно в процесс принятия изменений.


Новое значение статуса добавляется методом Create. Помимо результата и контекста, необходимо передать текст, который увидит пользователь. Дополнительно можно передать URL: в этом случае метка статуса на форме PR станет ссылкой и пользователь сможет перейти на страницу с деталями статуса.


Вызов метода приводит в созданию новой записи статусов PR. В рамках одного контекста последнее добавленное значение статуса считается активным. Более ранние записи статуса не видны из интерфейса UI, но их можно получить с помощью метода List.


Для состояния статусов на предыдущей картинке реальный список статусов для пулл-реквеста может быть таким:


{
  "value": [
    {
      "id": 1,
      "state": "failed",
      "description": "PR title format",
      "context": "@{name=check-title; genre=my-pr-policy}",
      "creationDate": "2018-11-06T18:35:57.0324172Z",
      "updatedDate": "2018-11-06T18:35:57.0324172Z",
      "createdBy": "Masked value",
      "targetUrl": null
    },
    {
      "id": 2,
      "state": "failed",
      "description": "Build for last update",
      "context": "@{name=check-build; genre=my-pr-policy}",
      "creationDate": "2018-11-06T18:35:57.5167963Z",
      "updatedDate": "2018-11-06T18:35:57.5167963Z",
      "createdBy": "Masked value",
      "targetUrl": null
    },
    {
      "id": 3,
      "state": "succeeded",
      "description": "No offset from develop",
      "context": "@{name=check-offset; genre=my-pr-policy}",
      "creationDate": "2018-11-06T18:35:57.782379Z",
      "updatedDate": "2018-11-06T18:35:57.782379Z",
      "createdBy": "Masked value",
      "targetUrl": null
    },
    {
      "id": 4,
      "state": "succeeded",
      "description": "PR title format",
      "context": "@{name=check-title; genre=my-pr-policy}",
      "creationDate": "2018-11-06T18:46:37.2627154Z",
      "updatedDate": "2018-11-06T18:46:37.2627154Z",
      "createdBy": "Masked value",
      "targetUrl": null
    },
    {
      "id": 5,
      "state": "succeeded",
      "description": "Build for last update",
      "context": "@{name=check-build; genre=my-pr-policy}",
      "creationDate": "2018-11-06T18:51:33.7920543Z",
      "updatedDate": "2018-11-06T18:51:33.7920543Z",
      "createdBy": "Masked value",
      "targetUrl": null
    },
    {
      "id": 6,
      "state": "failed",
      "description": "PR title format",
      "context": "@{name=check-title; genre=my-pr-policy}",
      "creationDate": "2018-11-06T18:53:44.3075889Z",
      "updatedDate": "2018-11-06T18:53:44.3075889Z",
      "createdBy": "Masked value",
      "targetUrl": null
    },
    {
      "id": 7,
      "state": "failed",
      "description": "Title format is not correct",
      "context": "@{name=check-title; genre=my-pr-policy}",
      "creationDate": "2018-11-06T19:26:11.3019433Z",
      "updatedDate": "2018-11-06T19:26:11.3019433Z",
      "createdBy": "Masked value",
      "targetUrl": null
    }
  ],
  "count": 7
}

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


Наконец, записи о статусах можно удалить методом Delete.


История изменения статусов PR может оказаться полезной — для последующего анализа. Поэтому у нас используется следующий метод изменения статусов:


  • Найти последнюю по времени запись статуса того же контекста
  • Если у нее те же значения результата, описания и ссылки что мы хотим добавить — ничего не делаем
  • Иначе, добавляем новую запись статуса.
    Это позволяет сохранять историю изменений, не сильно ее раздувая.

function Set-PullRequestStatus {
param(
    [Parameter (Mandatory = $true)]
    [string] $pullRequestId,

    [Parameter (Mandatory = $true)]
    [string] $state,

    [Parameter (Mandatory = $true)]
    [string] $description,

    [Parameter (Mandatory = $true)]
    [string] $contextName,

    [Parameter (Mandatory = $false)]
    [string] $contextGenre,

    [Parameter (Mandatory = $false)]
    [string] $targetUrl,

    [Parameter (Mandatory = $true)]
    [object] $context
)    

    $b = @{
        state = $state;
        description = $description;
        context = @{
            name = $contextName;
            genre = $contextGenre;
        };
        targetUrl = $targetUrl;
    }

    $body = ConvertTo-Json $b

    #
    # Get current list of statuses
    #

    $endpoint = (Get-ProjectBaseURL) + "/_apis/git/repositories/{repositoryId}/pullRequests/{pullRequestId}/statuses?api-version=4.1-preview.1"
    $res = Get-AzureRequestReqults -URI $endpoint -context ($context + @{pullRequestId = $pullRequestId})

    #
    # Try to find a status for a given context genre and name. Start looking from the last one. If found - check if it has same values.
    #

    $i = $res.count
    $foundSameStatus = $false

    while ($i -GT 0) {
        $r = $res.value[$i-1]

        if (($r.context.name -EQ $contextName) -AND ($r.context.genre -EQ $contextGenre)) {
            $foundSameStatus = ($r.state -EQ $state) -AND ($r.description -EQ $description) -AND ($r.targetUrl -EQ $targetUrl)

            break
        }
        $i--
    }

    $res = $r

    #
    # If same status / values was not found - add new record.
    #

    if (-not $foundSameStatus) {
        $endpoint = (Get-ProjectBaseURL) + "/_apis/git/repositories/{repositoryId}/pullRequests/{pullRequestId}/statuses?api-version=4.1-preview.1"

        $res = Get-AzureRequestReqults -URI $endpoint -context ($context + @{pullRequestId = $pullRequestId}) -method POST -query $body
    }

    return @{status = $res; status_changed = $(-not $foundSameStatus)}
}

Практическое применение


У нас принят довольно консервативный подход к принятию изменений в интеграционную ветку. Мы стараемся по максимуму протестировать изменение ещё на ветке фичи.


Вот некоторые задачи, которые мы решаем с помощью статусов PR, в рамках настраиваемых политик:


  1. Все PRы должены быть оформлены в едином стиле. Поэтому первый контроль который был создан — оформление заголовка PR. Мы проверяем его на соответствие регулярному выражению.
  2. Ветка PR не должена отставать от ветки куда ее вливают (проведена интеграция).
  3. Если в рамках PR изменяются файлы проекта БД, то также должны присутствовать файлы автотестов.
  4. Существует успешная сборка ветки по последнему изменению.
  5. Такая сборка успешно выкачена на тестовый стенд и автотесты прошли.

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


Дополнительный огромный плюс политик — они прозрачны для всех, и всегда на виду — на странице PR. О них больше не нужно напоминать.
Кроме того, для политик, которые не прошли проверку, мы показываем ссылку на страницы нашего Wiki с описанием политики и ожидаемых действий.


Техническая реализация выполнения политик


На данный момент логика политик реализована в виде скриптов powershell. За счёт высокоуровневых командлетов и хорошей объектной модели данных, код скрипта получается очень компактным. А возможность запускать скрипт по шагам интерактивно и ковыряться в переменных — существенно упрощает процесс разработки и отладки.


Кстати, после выпуска powershell core — скрипты можно выполнять и на других платформах (проверял на MacOS).


Запускаем скрипты в специальном релизе VSTS по расписанию примерно раз в пару часов. Может быть начнем запускать через шедулер почаще.


Такой подход, конечно, дает существенно более медленную реакцию, чем если реализовать то же самое в виде Azure Function и связать ее с VSTS через web hook. Но для нас было важнее реализовать проверки и посмотреть как это будет работать в процессе (MVP). Оперативность выполнения проверок пока не важна. Это будет на следующем этапе.


Реализация библиотек работы с VSTS REST API, которые использованы в проверках, плюс небольшой пример, реализующий некоторые из указанных политик можно посмотреть в репозитории на GitHub. Надеюсь, кому-нибудь окажется полезным.

Tags:
Hubs:
Total votes 4: ↑4 and ↓0+4
Comments2

Articles