Мы разрабатываем большое модульное UI-приложение, состоящее из большого количества плагинов с разными циклами релиза. Весь код располагается в одном репозитории, так что к разработчикам постоянно приходит QA-специалист и спрашивает: «А какой компонент поменялся? Какую версию выкладывать, чтобы проверить задачу?». Вопрос оказался актуален не только на UI (C#), но и на backend (Java). После наших опрометчивых обещаний все писать ручками я предложил автоматически формировать нужный список на базе изменившихся файлов в момент merge pull-request-а. В этой статье мы расскажем, как организовали это через расширение функциональности сборок на TeamCity (TC) без администраторских прав на сервере и установки внешних плагинов.
Для начала определим, что должен уметь наш инструмент:
Вот как это может выглядеть:
Теперь перечислим требования по реализации:
Из-за первого пункта в требованиях выше отпадает вариант с использованием Command Line Build Step-ов в TC, потому что:
Поэтому мы решили написать maven-plugin, который бы запускался maven-runner-ом. Собранную версию сохранили в бинарном хранилище, откуда она скачивалась прямо в процессе сборки. Можно было каждый раз собирать его прямо при сборке, но это заметно увеличило бы время работы сборки, и кроме того, создало бы дополнительную зависимость от отдельного VCS-root, что привело бы к лишним сборкам.
Теперь о тиражировании на большое количество build-конфигураций. Есть два способа:
Build template в нашем случае не очень подходит, так как сборки, в которых мы хотим его использовать, уже наследуются от своих build template. Также в этом случае в шагах/свойствах появляется много нерелевантных текущей сборке подробностей.
Таким образом, возможная техническая реализация выглядит так:
Начнем с того, что создадим простейший maven-plugin с названием ‘com.db.meteor:jira-commenter’. Для этого наследуем AbstractMojo, вот так:
Тут же приведен пример передачи параметра из mvn. Также в нашем pom.xml указываем, как это собирать, используя maven-plugin-plugin:
Плагин готов. Теперь сохраним изменения в наш VCS и сформируем отдельную сборку, которая сделает mvn:deploy. Это несложно: нужен единственный build step.
Теперь у нас есть отдельная сборка, которая будет собирать и выкладывать наш maven-plugin в локальный nexus (или artifactory, в зависимости от того, что вы используете для бинарных артефактов).
Теперь запустим этот плагин на TeamCity, чтобы убедиться, что идем в правильном направлении. Для этого сделаем отдельную сборку, в которую не будем подключать никаких VCS-root-ов и добавим единственный шаг – запуск maven, в котором в goals укажем наш плагин и передадим наш параметр:
Так при запуске maven сам скачает из нужного места наш плагин, положит в локальный кэш и запустит указанное mojo (в нашем случае — stampJira).
В Maven 2 как версию еще можно было указать LATEST, но с третьей версии приходится указывать точную версию. Запустив сборку, убедимся, что наш плагин скачивается и выполняется:
Передавать параметры через maven неудобно, но, к счастью, существует альтернатива. У TeamCity есть REST API. Сам REST API достаточно мощный и позволяет узнавать почти всё о сборках, агентах и т.п. Детально останавливаться на нем я не буду, для этого есть отличная подробная документация.
Я использовал получение информации о текущем статусе сборки и о проблемах, которые возникли (тесты/еще что-то/build output). Во время работы сборки ее статус будет RUNNING или FAILING, исходя из этого мы и будем определять, успешная сборка или нет.
Для доступа к API нужны логин и пароль. Не используйте свои собственные, так как при старте сборки генерируются одноразовый логин и пароль, которые доступны через параметр teamcity:
Еще понадобятся id сборки, которые передадим аналогичным образом.
Теперь стоит интегрировать наш плагин с Jira, чтобы ходить туда и обновлять нужные задачи. Не будем подробно здесь останавливаться, так как это несколько выходит за рамки статьи. Мы использовали для этого библиотеку com.atlassian.jira:jira-rest-java-client, на stackoverflow много примеров ее использования.
Покажу на примере, как создается meta-runner. Сначала надо найти редко используемый пункт меню и придумать название для нового meta-runner-а.
После извлечения мы окажемся на вкладке с meta-runner-ами проекта. Там можно будет его редактировать и обновлять, если нужно:
Технически meta-runner – это xml-описание шага (шагов) для выполнения, и он доступен так же, как и любой другой build step. У меня, например, получился такой:
При желании можно написать такой XML самому, но получить начальный из сборки проще. Редактировать же его можно прямо здесь изменением XML. Мы, например, меняем так версию сборки.
Теперь возможно в любой сборке в текущем проекте добавить новый buildstep:
Причем параметры, которые использовались в тестовой сборке, теперь доступны для заполнения в нашем build step, например, teamcity.build.branch:
Отлично, теперь мы можем попросить наших коллег и всех заинтересованных добавить себе в сборки этот шаг и радоваться жизни.
Замечание: после извлечения meta-runner-a он никак не синхронизируется с тем проектом, откуда его извлекли. Поэтому если хотите что-то поменять – меняйте в нем.
Итак, теперь при успешной сборке будет выполняться наш код. А если сборка не прошла? В advanced options существует полезная опция: «когда выполнять этот шаг»:
Тут выбирается подходящее условие выполнения. Для текущей задачи поставим «Even if some of the previous steps failed».
Вот так удалось сделать счастливее наших QA-инженеров – теперь после каждого merge request в master или release ветки в соответствующей задаче в JIRA пишется подробный комментарий о том, что поменялось и что разворачивать для тестирования.
Условия задачи
Для начала определим, что должен уметь наш инструмент:
- При успешной сборке проставлять в JIRA номер сборки и указывать в комментарии дополнительную информацию (в какой ветке починили, какие компоненты развертывать и т.п.)
- При неуспешной сборке посылать письмо конкретным людям с конкретным шаблоном и описанием проблемы.
Вот как это может выглядеть:
Теперь перечислим требования по реализации:
- Некий нетривиальный код (логика) после сборки.
- Использование Windows- и Linux-агентов.
- Ничего нестандартного на агентах.
- Легкая тиражируемость на build-конфигурации.
Выбираем решение
Из-за первого пункта в требованиях выше отпадает вариант с использованием Command Line Build Step-ов в TC, потому что:
- Сложную логику писать на этом сложно (хотя и возможно);
- Специалистов, знающих bash/cmd/powershell, мало;
- Это не версионируется;
- Один и тот же код на Windows и на Linux агентах не запустить.
Поэтому мы решили написать maven-plugin, который бы запускался maven-runner-ом. Собранную версию сохранили в бинарном хранилище, откуда она скачивалась прямо в процессе сборки. Можно было каждый раз собирать его прямо при сборке, но это заметно увеличило бы время работы сборки, и кроме того, создало бы дополнительную зависимость от отдельного VCS-root, что привело бы к лишним сборкам.
Как вариант, можно делать NuGet-пакет с нужным exe-файлом, скачивать его с приватного NuGet-сервера и запускать. Хотя в нашем случае из-за Linux-серверов вариант с .NET-программами оказался недоступным, но в другом проекте подобный подход хорошо показал себя.
Теперь о тиражировании на большое количество build-конфигураций. Есть два способа:
- Build template. От него можно наследовать нашу сборку, и таким образом изменять в одном месте различные параметры/шаги/свойства.
- Meta-Runner. Мимикрирует под runner, никак не отображается в сборке.
Build template в нашем случае не очень подходит, так как сборки, в которых мы хотим его использовать, уже наследуются от своих build template. Также в этом случае в шагах/свойствах появляется много нерелевантных текущей сборке подробностей.
Таким образом, возможная техническая реализация выглядит так:
- Создается отдельный репозиторий, где хранится код нашего плагина.
- Плагин собирается отдельной сборкой и выкладывается в хранилище бинарных артефактов (Nexus или Artifactory).
- Для целевых сборок неким креативным способом запускаем этот плагин.
- Всё это оформляем как meta-runner.
Простейший maven-plugin
Начнем с того, что создадим простейший maven-plugin с названием ‘com.db.meteor:jira-commenter’. Для этого наследуем AbstractMojo, вот так:
@Mojo( name = "stampJira", requiresProject = false)
public class MainMojo extends AbstractMojo {
@Parameter( property = "jiraCommenter.branch")
public String branchName;
public void execute() throws MojoExecutionException{
getLog().info(branchName);
}
}
Тут же приведен пример передачи параметра из mvn. Также в нашем pom.xml указываем, как это собирать, используя maven-plugin-plugin:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.2</version>
<configuration>
<!-- see http://jira.codehaus.org/browse/MNG-5346 -->
<skipErrorNoDescriptorsFound</b>>true</<b>skipErrorNoDescriptorsFound>
</configuration>
<executions>
<execution>
<id>mojo-descriptor</id>
<goals>
<goal>descriptor</goal>
</goals>
</execution>
</executions>
</plugin>
Плагин готов. Теперь сохраним изменения в наш VCS и сформируем отдельную сборку, которая сделает mvn:deploy. Это несложно: нужен единственный build step.
Теперь у нас есть отдельная сборка, которая будет собирать и выкладывать наш maven-plugin в локальный nexus (или artifactory, в зависимости от того, что вы используете для бинарных артефактов).
Запуск на TeamCity
Теперь запустим этот плагин на TeamCity, чтобы убедиться, что идем в правильном направлении. Для этого сделаем отдельную сборку, в которую не будем подключать никаких VCS-root-ов и добавим единственный шаг – запуск maven, в котором в goals укажем наш плагин и передадим наш параметр:
Так при запуске maven сам скачает из нужного места наш плагин, положит в локальный кэш и запустит указанное mojo (в нашем случае — stampJira).
В Maven 2 как версию еще можно было указать LATEST, но с третьей версии приходится указывать точную версию. Запустив сборку, убедимся, что наш плагин скачивается и выполняется:
Передавать параметры через maven неудобно, но, к счастью, существует альтернатива. У TeamCity есть REST API. Сам REST API достаточно мощный и позволяет узнавать почти всё о сборках, агентах и т.п. Детально останавливаться на нем я не буду, для этого есть отличная подробная документация.
Я использовал получение информации о текущем статусе сборки и о проблемах, которые возникли (тесты/еще что-то/build output). Во время работы сборки ее статус будет RUNNING или FAILING, исходя из этого мы и будем определять, успешная сборка или нет.
Для доступа к API нужны логин и пароль. Не используйте свои собственные, так как при старте сборки генерируются одноразовый логин и пароль, которые доступны через параметр teamcity:
Еще понадобятся id сборки, которые передадим аналогичным образом.
Теперь стоит интегрировать наш плагин с Jira, чтобы ходить туда и обновлять нужные задачи. Не будем подробно здесь останавливаться, так как это несколько выходит за рамки статьи. Мы использовали для этого библиотеку com.atlassian.jira:jira-rest-java-client, на stackoverflow много примеров ее использования.
Extract meta-runner
Покажу на примере, как создается meta-runner. Сначала надо найти редко используемый пункт меню и придумать название для нового meta-runner-а.
После извлечения мы окажемся на вкладке с meta-runner-ами проекта. Там можно будет его редактировать и обновлять, если нужно:
Технически meta-runner – это xml-описание шага (шагов) для выполнения, и он доступен так же, как и любой другой build step. У меня, например, получился такой:
<?xml version="1.0" encoding="UTF-8"?>
<meta-runner name="Temporary for article">
<description>Temporary for article</description>
<settings>
<parameters>
<param name="maven.security" value="%teamcity.agent.home.dir%/.m2/settings-security.xml" spec="text validationMode='any' display='hidden'" />
<param name="teamcity.build.branch" value="master" />
</parameters>
<build-runners>
<runner name="Launch Jira commenter." type="Maven2">
<parameters>
<param name="goals" value="com.db.meteor.tools:jira-commenter:master-1.0.0.33:stampJira" />
<param name="maven.home" value="" />
<param name="mavenSelection" value="mavenSelection:default" />
<param name="runnerArgs" value="-DjiraCommenter.branch=%teamcity.build.branch%" />
<param name="teamcity.coverage.emma.include.source" value="true" />
<param name="teamcity.coverage.emma.instr.parameters" value="-ix -*Test*" />
<param name="teamcity.coverage.idea.includePatterns" value="*" />
<param name="teamcity.coverage.jacoco.patterns" value="+:*" />
<param name="teamcity.step.mode" value="default" />
<param name="userSettingsPath" value="%teamcity.agent.home.dir%/.m2/settings.xml" />
<param name="userSettingsSelection" value="userSettingsSelection:byPath" />
</parameters>
</runner>
</build-runners>
<requirements />
</settings>
</meta-runner>
При желании можно написать такой XML самому, но получить начальный из сборки проще. Редактировать же его можно прямо здесь изменением XML. Мы, например, меняем так версию сборки.
Теперь возможно в любой сборке в текущем проекте добавить новый buildstep:
Причем параметры, которые использовались в тестовой сборке, теперь доступны для заполнения в нашем build step, например, teamcity.build.branch:
Отлично, теперь мы можем попросить наших коллег и всех заинтересованных добавить себе в сборки этот шаг и радоваться жизни.
Замечание: после извлечения meta-runner-a он никак не синхронизируется с тем проектом, откуда его извлекли. Поэтому если хотите что-то поменять – меняйте в нем.
Run always?
Итак, теперь при успешной сборке будет выполняться наш код. А если сборка не прошла? В advanced options существует полезная опция: «когда выполнять этот шаг»:
Тут выбирается подходящее условие выполнения. Для текущей задачи поставим «Even if some of the previous steps failed».
Вот так удалось сделать счастливее наших QA-инженеров – теперь после каждого merge request в master или release ветки в соответствующей задаче в JIRA пишется подробный комментарий о том, что поменялось и что разворачивать для тестирования.