На операционных системах Windows администраторы традиционно используют PowerShell как средство автоматизации. Он позволяет выполнять такие команды командной строки как cd, dir и предоставляет удобный доступ к различным API: COM, WMI, Active Directory и т.д. PowerShell хорош для автоматизации задач на локальных и удаленных системах с помощью командлетов - упрощенных команд. Изначально он был построен на .NET Framework, позднее, когда в 2016 году стал кроссплатформенным, перешел на .NET Core, и код его стал открытым. Хотя PowerShell имеет .NET в своей основе, синтаксис его сценариев отличается от языка C#, к которому привыкли разработчики .NET, многие из которых занимаются и настройкой сборок. Для них альтернативой сценариям PowerShell могут быть сценарии C#, синтаксис которых, практически, идентичен синтаксису C#. В TeamCity 2021.2 появился специальный раннер для их поддержки. Он работает в 2-х вариантах:

  • Для запуска сценария из файла, обычно имеющего расширение .csx

  • Для запуска сценария, редактируемого через пользовательский интерфейс шага сборки TeamCity

Для первого варианта указывается путь к файлу C# сценария, для второго - его содержимое, в остальном они похожи. Пример Hello World дает общее представление.

Hello World

Этот пример не представляет из себя практической ценности, он  помогает разобраться в базовых возможностях, а его результат - это вывод приветствия Hello World from project C# Script в журнал сборки:

TeamCity DSL для этого шага сборки лаконичен:

object HelloWorldBuildType: BuildType({
  name = "Say hello"
  steps {
    csharpScript {
      content = "WriteLine(\"Hello World from project \" + Args[0])"
      arguments = "\"%system.teamcity.projectName%\""
      dockerImage = Settings.dockerImageRuntime
      dockerImagePlatform = CSharpScriptCustomBuildStep.ImagePlatform.Linux
    }
  }
})

Здесь определены несколько параметров:

  • content, соответствует полю C# Script и определяет содержимое сценария

  • arguments, поле Script parameters - определяет необязательные параметры сценария, здесь это название проекта %system.teamcity.projectName%, фактически, это C# Script

  • dockerImage, поле Run step within Docker container - содержит имя докер образа, для контейнера, в котором будет запущен сценарий

  • dockerImagePlatform, поле Docker image platform - указывает использовать Linux докер контейнер для случаев, если докер хост поддерживает мультиплатформенность

В этом примере не задействовано поле NuGet package sources. В нем можно определить один или несколько источников NuGet пакетов, разделенных пробелами. Это поле позволяет использовать произвольные источники NuGet пакетов, помимо https://api.nuget.org/v3/index.json, включая приватные и встроенные в TeamCity. Еще одно поле TeamCity C# script tool не определено, но оно имеет значение по умолчанию - Default и указывает использовать версию по умолчанию инструмента TeamCity C# script tool. Поэтому перед первым использованием ранера нужно загрузить этот инструмент через инструменты агентов TeamCity и определить версию по умолчанию. Фактически, вы загрузите NuGet пакет, который содержит .NET tool для запуска C# сценариев.

C# script tool

Microsoft предоставляет кроссплатформенный инструмент выполнения сценариев для F#, но для C#, на данный момент есть только версия для Windows. Отчасти по этой причине TeamCity сделал альтернативный инструмент. Вторая причина - это более глубокая интеграция с TeamCity. По сравнению с инструментом от Microsoft инструмент от TeamCity имеет ряд дополнительных возможностей:

  • Кроссплатформенность

  • Интеграция с TeamCity, а также возможность использовать приватные и встроенные в TeamCity источники NuGet пакетов

  • REPL директива для зависимостей на NuGet пакеты

  • Дополнительный API, который планируется расширить, чтобы поддержать наиболее востребованные сценарии использования

Под капотом используется Microsoft.CodeAnalysis.Scripting для запуска C# сценариев, NuGet.Build.Tasks для работы с NuGet пакетами из этих сценариев, пакет TeamCity.ServiceMessages для отправки сервисных сообщений в TeamCity и Pure.DI чтобы связать все это вместе. Для работы инструмента необходим .NET 6 Runtime. Поэтому в примерах все сценарии C# в TeamCity выполняются в докер контейнере, иначе .NET Runtime должен быть заранее установлен. Предполагается несколько вариантов использования этого .NET инструмента:

  • режим командной строки

  • интерактивный режим

  • в специальном ранере TeamCity (примере выше)

Для первых двух установка иструмента выполняется командой:

dotnet tool install TeamCity.csi -g --version <version>

где в качестве version лучше использовать наиболее свежую версию из NuGet репозитория.

Для работы иструмента в интерактивном режиме запустите:

dotnet csi

Для выполнения некоторого сценария MyScript.csx в режиме командной строки:

dotnet csi MyScript.csx

Подробнее об этом можно узнать на здесь.

Build and deploy

В этом примере конфигурация сборки TeamCity состоит из 6 шагов, на двух из которых остановимся подробнее.

Первый шаг запускает C# сценарий, в котором восстанавливается последняя версия пакета с именем MySampleLib из репозитория NuGet.org. Определяется его текущая версия и рассчитывается новая версия. Второй и третий шаги строят и тестируют библиотеку MySampleLib, используя значение новой версии. На четвертом шаге создается Telegram бот, который публикует промежуточные результаты сборки с отчетом о тестировании, и проводит голосование среди заинтересованных. На этом голосовании решается вопрос о том, готова ли библиотека к публикации в репозиторий NuGet. Если в течение определенного для голосования времени все заинтересованные единогласно решают, что библиотека готова, TeamCity упаковывает построенную и проверенную ранее сборку в NuGet пакет с именем MySampleLib, и версией, рассчитанной ранее. На заключительном шаге TeamCity публикует этот пакет в NuGet репозиторий. Если хотя бы один человек проголосовал против или истекло время голосования, сборка завершается с описанием причины. Рассмотрим подробнее первый шаг, где определяется следующая версия NuGet пакета MySampleLib.

C# сценарий выше принимает имя NuGet пакета как единственный параметр в поле Script parameters. Его значение доступно как 0-ой элемент глобального массива Args. В строке #3 для доступа к NuGet API вызывается глобальная функция GetService. #4 восстанавливает пакет с именем MySampleLib (из Args[0]) самой свежей версии и возвращает информацию о нем, и о всех пакетах, от которых он зависит. В строке #7 рассчитывается значение новой версии пакета MySampleLib. В строке #2 это значение сохраняется в глобальном словаре Props с индексом version, и будет доступно во всех следующих шагах сборки TeamCity: как в сценариях C#, так и в таких командах, как dotnet build, dotnet test, dotnet pack, как если бы он был передан им через аргумент командной строки -p:version=1.0.9. В TeamCity это значение будет представлено как системный параметр system.version, на него можно ссылаться используя выражение %system.version%. Далее, на других этапах сборки в этом примере, значение новой версии будет использовано при построении, тестировании, упаковке пакета и его публикации без каких-либо дополнительных усилий. Строка #11 выводит актуальную версию пакета MySampleLib в журнал сборки цветом Success - зеленый цвет в палитре TeamCity.

На третьем шаге сборки проводится голосование в Telegram канале. Перед его запуском уже есть готовая библиотека и результаты ее тестирования.

C# сценарий голосования находится в файле Samples/Scripts/TelegramBot.csx. Сначала сценарий получает токен Telegram бота и продолжительность голосования из системных параметров TeamCity, определенных в родительском проекте:

if(!Props.TryGetValue("telegram.bot.token", out var token))
{
    throw ...
}

if(!Props.TryGetValue("telegram.bot.poll.timeout", out var timeoutStr) ||
   !TimeSpan.TryParse(timeoutStr, out var timeout))
{
    throw ...
}

Затем сценарий создает Telegram бот для голосования, используя пакеты Telegram.Bot и Telegram.Bot.Extensions.Polling:

#r "nuget: Telegram.Bot, 15.6.0"
#r "nuget: Telegram.Bot.Extensions.Polling, 0.2.0"

Для участия в голосовании каждый заинтересованный в сборке должен выполнить команду /start в Telegram канале бота. Сценарий присылает каждому ссылку на текущую сборку и проводит голосование:

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

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

Мы будем рады вашим пожеланиям или PR в этом репозитории для расширения встроенного API, например, для более глубокой интеграции с TeamCity или для поддержки часто используемых сценариев.

Итог

TeamCity C# Script раннер может быть полезен для случаев, когда необходимо эффективно автоматизировать какой либо аспект сборки, силами .NET разработчиков или администраторами, знакомыми с синтаксисом C#.

Ресурсы

Репозиторий инструмента C# script tool: JetBrains/teamcity-csharp-interactive

TeamCity DSL: settings.kts

Скрипт для шага с голосованием: TelegramBot.csx

Репозиторий TeamCity C# Script раннера: JetBrains/teamcity-dotnet-plugin

Demo проект: teamcity.jetbrains.com

Комментарии (4)


  1. Getequ
    02.11.2021 18:30
    +1

    По теме - отлично!

    Не по теме - это так принято в jetBrains писать без пробелов (после if) или частный случай?

    "if(!Props.TryGetValue("telegram.bot.token", out var token))"


    1. NikolayPyanikov Автор
      02.11.2021 20:07
      +1

      По теме - отлично!

      Рад что было полезно.

      Не по теме - это так принято в jetBrains писать без пробелов (после if) или частный случай?

      Могу сказать только про себя - чаще добавляю пробел :)


  1. maxcat
    04.11.2021 01:43

    Хорошо. Но на домашней машине просто использую для автоматизации c# interactive или roslyn pad


  1. soondook
    24.11.2021 17:58

    Кто-нибудь нашёл этому практическое применение?