Мы разрабатываем большое модульное UI-приложение, состоящее из большого количества плагинов с разными циклами релиза. Весь код располагается в одном репозитории, так что к разработчикам постоянно приходит QA-специалист и спрашивает: «А какой компонент поменялся? Какую версию выкладывать, чтобы проверить задачу?». Вопрос оказался актуален не только на UI (C#), но и на backend (Java). После наших опрометчивых обещаний все писать ручками я предложил автоматически формировать нужный список на базе изменившихся файлов в момент merge pull-request-а. В этой статье мы расскажем, как организовали это через расширение функциональности сборок на TeamCity (TC) без администраторских прав на сервере и установки внешних плагинов.


Условия задачи


Для начала определим, что должен уметь наш инструмент:

  1. При успешной сборке проставлять в JIRA номер сборки и указывать в комментарии дополнительную информацию (в какой ветке починили, какие компоненты развертывать и т.п.)
  2. При неуспешной сборке посылать письмо конкретным людям с конкретным шаблоном и описанием проблемы.

Вот как это может выглядеть:



Теперь перечислим требования по реализации:

  1. Некий нетривиальный код (логика) после сборки.
  2. Использование Windows- и Linux-агентов.
  3. Ничего нестандартного на агентах.
  4. Легкая тиражируемость на build-конфигурации.

Выбираем решение


Из-за первого пункта в требованиях выше отпадает вариант с использованием Command Line Build Step-ов в TC, потому что:

  1. Сложную логику писать на этом сложно (хотя и возможно);
  2. Специалистов, знающих bash/cmd/powershell, мало;
  3. Это не версионируется;
  4. Один и тот же код на Windows и на Linux агентах не запустить.

Поэтому мы решили написать maven-plugin, который бы запускался maven-runner-ом. Собранную версию сохранили в бинарном хранилище, откуда она скачивалась прямо в процессе сборки. Можно было каждый раз собирать его прямо при сборке, но это заметно увеличило бы время работы сборки, и кроме того, создало бы дополнительную зависимость от отдельного VCS-root, что привело бы к лишним сборкам.

Как вариант, можно делать NuGet-пакет с нужным exe-файлом, скачивать его с приватного NuGet-сервера и запускать. Хотя в нашем случае из-за Linux-серверов вариант с .NET-программами оказался недоступным, но в другом проекте подобный подход хорошо показал себя.

Теперь о тиражировании на большое количество build-конфигураций. Есть два способа:

  1. Build template. От него можно наследовать нашу сборку, и таким образом изменять в одном месте различные параметры/шаги/свойства.
  2. Meta-Runner. Мимикрирует под runner, никак не отображается в сборке.

Build template в нашем случае не очень подходит, так как сборки, в которых мы хотим его использовать, уже наследуются от своих build template. Также в этом случае в шагах/свойствах появляется много нерелевантных текущей сборке подробностей.

Таким образом, возможная техническая реализация выглядит так:

  1. Создается отдельный репозиторий, где хранится код нашего плагина.
  2. Плагин собирается отдельной сборкой и выкладывается в хранилище бинарных артефактов (Nexus или Artifactory).
  3. Для целевых сборок неким креативным способом запускаем этот плагин.
  4. Всё это оформляем как 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 пишется подробный комментарий о том, что поменялось и что разворачивать для тестирования.

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