Заметки консультанта

Шамрай Александр Владимирович

Archive for Январь 2020

Сборка дельта-пакетов с помощью Azure DevOps Pipeline и GIT

Posted by Shamrai Alexander на 31 января, 2020

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

Какие шаги нам необходимо будет здесь выполнить? Рассмотрим возможные на основе непрерывной интеграции:

  1. Получить список файлов, которые изменились от предыдущей версии.
  2. Скопировать измененные файлы.

Получить список измененных файлов

Список изменившихся от последней версии нужно получать, исходя из системы ветвления и поставки изменений. Например, если выполняется разработка в отдельных ветвях и слияние в основной поток выполняется одним коммитом (через стандартный мерж), то достаточно сравниться с предыдущим коммитом. Однако, если выполняется работа в одном потоке или выполняется слияние с перебазированием, то тут желательно выставить для сборки установку тега и сравниваться между последним коммитом и последним тегом.

Рисунок 1. Установка тега в сборке

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

$changes = git diff —name-only —relative —diff-filter AMR HEAD^ HEAD .

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

$last_tag = git describe —tags —abbrev=0 —match «[0-9]*»

$changes = git diff —name-only —relative —diff-filter AMR $last_tag HEAD .

Скопировать измененные файлы

Чтобы скопировать файлы с сохранением структуры, можно использовать команды New-Item и copy-item для форсирования создания структуры каталогов. При этом как целевая директория будет каталог для артефактов на агенте сборки. Пример:

$targetfolder = «$(Build.StagingDirectory)» + «/»

function CopyFiles{

param( [string]$source )

$target = $targetfolder + $source

New-Item -Force $target

copy-item $source $target -Force

}

В результате шаг в сборке может выглядеть следующим образом:

Рисунок 2. Шаг копирования изменившихся файлов

Далее необходимо добавить финальный шаг публикации артефактов:

Рисунок 3. Публикация артефактов

Скрипты для копирования файлов можно найти здесь:

Posted in azure, devops, Microsoft, Team Foundation Server, Visual Studio | Отмечено: , | Leave a Comment »

Автоматизация изменения состояния родительских рабочих элементов Azure DevOps при изменении дочерних через Azure Logic App

Posted by Shamrai Alexander на 8 января, 2020

Автоматизация изменений объектов Azure DevOps может выполняться различными методами. Один из них – это проект Visual Studio с использованием соответствующий nuget пакет, который работает через данный Rest Api. Пример его использования приводился здесь: Автоматизация изменений состояний рабочих элементов Azure DevOps на основе состояний дочерних элементов. Однако использование данного метода требует наличия навыков в программировании. В данной статье мы рассмотрим возможности автоматизации объектов Azure DevOps через Azure Logic App, который позволяет проектировать взаимодействие с Azure DevOps через визуальный редактор без наличия навыков программирования. Кроме этого, нет необходимости искать дополнительное место для развертывания веб-сервисов или консольных задач.

Рассматриваться будут два примера:

  1. Активирование родителя, если любой дочерний элемент перешел в состояние Активно.
  2. Закрытие родителя, если все дочерние задачи закрыты.

Логика обработки будет простая:

  1. Создать триггер, который будет отслеживать изменение рабочих элементов:
    1. Задачи, которые перешли в состояние Активно.
    2. Задачи, которые были закрыты.
  2. Проанализировать необходимость изменения родителя:
    1. Родительский элемент находится в состоянии Новый.
    2. Родительский элемент еще не закрыт и все дочерние задачи закрыты.
  3. Выполнить обновление родительского рабочего элемента.

Активирование родителя, если любой дочерний элемент перешел в состояние Активно

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

  • Создать свое приложение Logic App:

Рисунок 1. Создание приложения Logic App

  • Использовать пустой шаблон

Рисунок 2. Пустой шаблон

  • Найти триггеры для DevOps сервисов

Рисунок 3. Поиск триггеров Azure DevOps

  • Создать триггер для отслеживания изменения рабочих элементов:

Рисунок 4. Триггер на изменение рабочего элемента

  • Войти в необходимую подписку Azure DevOps:

Рисунок 5. Вход в подписку Azure DevOps

  • После регистрации в подписке, нужно указать параметры для триггера (такие как, организация подписки, имя командного проекта и отслеживаемый тип рабочего элемента):

Рисунок 6. Триггер на изменение задач

  • Добавить новый шаг:

Рисунок 7. Добавление нового шага

  • И выбрать категорию переменных

Рисунок 8. Категория для переменных

  • Выбрать инициализацию переменных:

Рисунок 9. Инициализация переменной

  • Указать наименование и тип переменной. В данном случае будет переменная для хранения ссылки на родительский элемент.

Рисунок 10. Переменная для хранения ссылки на родительский рабочий элемент

  • Создать вторую переменную с типом массив для хранения результата паркинга ссылки на родительский рабочий элемент, чтобы получить доступ к его идентификатору:

Рисунок 11. Переменная массив

  • Триггер для отслеживания рабочих элементов возвращает последние значения для рабочих элементов, но какие поля изменились не видно. В этом случае мы не знаем изменилось ли поле состояния. Поэтому необходимо получить предыдущую версию рабочего элемента, для которого сработал триггер. Но такого метода нет в текущем наборе активностей, поэтому можно использовать универсальный метод Send a HTTP request to Azure DevOps, который отправит следующий запрос: Revisions – Get.

Рисунок 12. Метод Send a HTTP request to Azure DevOps

  • Наименование шага можно сразу поменять:

Рисунок 13. Переименование шага

  • Вставить шаблон запроса в задачу (_apis/wit/workItems/{id}/revisions/{revisionNumber}?api-version=5.1):

Рисунок 14. Относительный путь для вызова Rest Api — получение ревизии рабочего элемента

  • Вместо {id} указать идентификатор рабочего элемента из триггера:

Рисунок 15. Определение идентификатора

  • Вместо {revisionNumber} вычисляем предыдущий номер версии рабочего элемента через формулу add(triggerBody()?[‘fields’]?[‘System_Rev’],-1):

Рисунок 16. Определение номера ревизии

  • В результате шаг будет выглядеть следующим образом:

Рисунок 17. Команда получения ревизии рабочего элемента

  • Добавить шаг для разбора json ответа от сервиса:

Рисунок 18. Разбор JSON

  • В поле Content вставить тело из запроса получения предыдущей версии:

Рисунок 19. Содержимое для разбора JSON

  • В поле Schema вставить следующее содержимое для извлечения поля состояние:
{

«properties»: {

«_links»: {

«type»: «object»

},

«fields»: {

«properties»: {

«System.State»: {

«type»: «string»

}

},

«type»: «object»

},

«id»: {

«type»: «integer»

},

«rev»: {

«type»: «integer»

},

«url»: {

«type»: «string»

}

},

«type»: «object»

}

Рисунок 20. Схема для разбора ответа

  • Сравнить текущее состояние с предыдущим. Для этого следующим шаге нужно добавить условие из категории Control:

Рисунок 21. Шаг — условие

  • Указать, что предыдущее состояние должно быть New, а текущее Active.

Рисунок 22. Выбор состояния в предыдущей ревизии

Рисунок 23. Выбор состояния для текущей версии

Рисунок 24. Сравнение состояний в условии

  • Далее необходимо получить ссылку на родителя нашей задачи. Для этого используем метод Send a HTTP request to Azure DevOps и ссылку Work Items — Get Work Item, в которой идентификатор используем из триггера.

Рисунок 25. Относительный путь с идентификатором для вызова Rest Api — получение связей рабочего элемента

  • Результат запроса также нужно разобрать c использованием следующей схемы, которая позволит получить только связи:
{

«properties»: {

«_links»: {

«type»: «object»

},

«fields»: {

«type»: «object»

},

«id»: {

«type»: «integer»

},

«relations»: {

«items»: {

«properties»: {

«attributes»: {

«properties»: {

«isLocked»: {

«type»: «boolean»

},

«name»: {

«type»: «string»

}

},

«type»: «object»

},

«rel»: {

«type»: «string»

},

«url»: {

«type»: «string»

}

},

«required»: [

«rel»,

«url»,

«attributes»

],

«type»: «object»

},

«type»: «array»

},

«rev»: {

«type»: «integer»

},

«url»: {

«type»: «string»

}

},

«type»: «object»

}

Рисунок 26. Разбор ответа

  • Далее необходимо перебрать ссылки и вычислить родителя. Для этого необходимо добавить цикл For each.

Рисунок 27. Цикл для перебора связей

  • Как входной параметр для шага указываем relations из предыдущего шага. Каждый элемент этого массива будет перебираться в цикле

Рисунок 28. Выбор источника для цикла

  • Далее необходимо добавить условия сравнения текущего объекта цикла и его свойства rel (через выражение items(‘For_each’)?[‘rel’]) со значением System.LinkTypes.Hierarchy-Reverse.

Рисунок 29. Выбор поля наименования связи

Рисунок 30. Сравнение с родительским типом связи

  • В блок If true добавить шаг копирования ссылки родительского рабочего элемента через действие Set variable в переменную ParentUrl из текущего элемента через выражение items(‘For_each’)?[‘url’]:

Рисунок 31. Шаг для установки значения переменной

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

  • После перебора всех связей добавим еще одно условие, которое проверит был ли родитель через проверку значения в переменной ParentUrl. Если оно не пустое, то мы можем пробовать обновлять родительский рабочий элемент.

Рисунок 33. Сравнение значения в ParentUrl

  • Если ParentUrl содержит значение, то создаем новый шаг для получения информации о родителе:

Рисунок 34. Шаг получения детальной информации о рабочем элементе

  • В нашем случае, мы ожидаем, что будет рабочий элемент с типом User Story. Идентификатор мы можем извлечь из ссылки на рабочий элемент через выражение: split(variables(‘Parent Url’), ‘_apis/wit/workItems/’)[1].

Рисунок 35. Получить детальную информацию о рабочем элементе

  • Далее необходимо проверить, что рабочий элемент до сих пор находится в состоянии New через шаг Condition:

Рисунок 36. Выбор поля для сравнения

Рисунок 37. Сравнение состояния

  • В блок If true добавляем шаг обновления рабочего элемента:

Рисунок 38. Выбор шага обновления рабочего элемента

  • Указать, что мы хотим обновить состояние рабочего элемента:

Рисунок 39. Обновление состояния рабочего элемента

После сохранения данной последовательности, результат работы обновления, когда сработает триггер, может быть отображен в журнале в следующем виде:

Рисунок 40. Журнал выполнения правила

Закрытие родителя, если все дочерние задачи закрыты

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

  1. Сначала клонируем предыдущую задачу:

Рисунок 41. Клонирование приложения

  1. Начнем редактирование действий. Сначала добавим дополнительную переменную, которая будет говорить о необходимости обновления родителя.

Рисунок 42. Вставка действия в существующую последовательность

  1. Укажем наименование и значение по умолчанию:

Рисунок 43. Наименование и значение по умолчанию.

  1. Изменим первое условие на следующую логику: предыдущее состояние не равно текущему (т.е. состояние изменилось), текущее состояние равно Closed:

Рисунок 44. Проверка, что задача была закрыта

  1. Удаляем последнюю задачу обновления рабочего элемента:

Рисунок 45. Удаление задачи

  1. И вставляем новую для получения дочерних рабочих элементов:

Рисунок 46. Действие для получения дочерних рабочих элементов

  1. В качестве идентификатора используем соответствующее свойство из шага Get work item details и указываем тип дочерних элементов Task:

Рисунок 47. Получение дочерних рабочих элементов

  1. Добавляем новый цикл For each, которые переберет все наши полученные дочерние элементы из шага Get work item children:

Рисунок 48. Цикл анализа дочерних элементов

  1. И вставляем новую проверку для анализа состояния каждого элемента. Доступ к элементу выполняется через выражение items(‘For_each_2’)?[‘System.State’]:

Рисунок 49. Выражение для поиска состояния текущего элемента в цикле

Или его можно выбрать через поиск состояния в динамическом содержимом:

Рисунок 50. Вставка состояния текущего элемента через динамическое содержимое

  1. Далее ищем незакрытый элемент и, если его находим, то в блоке If true присваиваем значение переменной UpdateState значение false.

Рисунок 51. Анализ необходимости обновления состояния

  1. Далее после шага Check Parent State, вставляем новый блок проверки условия. В нем проверяем переменную UpdateState. Если она истинна, то обновляем наш родительский рабочий элемент.

Рисунок 52. Обновление родительского рабочего элемента

Итого

В итоге мы получили сервис, который:

  1. Создан относительно быстро без необходимости развертывания каких-то сторонних сервисов, плагинов и т.д.
  2. Создан в графическом мастере без необходимости кодинга, компиляции и развертывания результатов сборки.
  3. Расширяем. На текущий момент еще немного существует встроенных действий, но вызов Send a HTTP request to Azure DevOps довольно универсален и покрывает основные потребности.
  4. Позволяет использовать стандартные возможности Azure для мониторинга работы сервиса, возможных ошибок, аудита истории и т.д.

Рисунок 53. Просмотр истории выполнения

Posted in azure, devops, Microsoft | Отмечено: , , | Leave a Comment »

 
%d такие блоггеры, как: