Написание плагина Дженкинс

Написание плагин Дженкинс

1. обзор

Jenkins - это сервер Continuous Integration с открытым исходным кодом, который позволяет создавать собственные плагины для конкретной задачи / среды.

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

2. Настроить

Первое, что нужно сделать, это настроить проект. Luckily, Jenkins provides convenient Maven archetypes для этого.

Просто запустите команду ниже из оболочки:

mvn archetype:generate -Dfilter=io.jenkins.archetypes:plugin

Мы получим следующий результат:

[INFO] Generating project in Interactive mode
[INFO] No archetype defined. Using maven-archetype-quickstart
  (org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
Choose archetype:
1: remote -> io.jenkins.archetypes:empty-plugin (Skeleton of
  a Jenkins plugin with a POM and an empty source tree.)
2: remote -> io.jenkins.archetypes:global-configuration-plugin
  (Skeleton of a Jenkins plugin with a POM and an example piece
  of global configuration.)
3: remote -> io.jenkins.archetypes:hello-world-plugin
  (Skeleton of a Jenkins plugin with a POM and an example build step.)

Теперь выберите первый вариант и определите группу / артефакт / пакет в интерактивном режиме. После этого необходимо внести изменения вpom.xml, поскольку он содержит такие записи, как<name>TODO Plugin</name>.

3. Дизайн плагина Jenkins

3.1. Точки расширения

Jenkins provides a number of extension points. Это интерфейсы или абстрактные классы, которые определяют контракты для конкретных вариантов использования и позволяют другим плагинам реализовывать их.

Например, каждая сборка состоит из нескольких этапов, например, “Checkout from VCS”,“Compile”,“Test”,“Assemble”, и т. д. Дженкинс определяет точку расширенияhudson.tasks.BuildStep, поэтому мы можем реализовать ее, чтобы предоставить настраиваемый шаг, который можно настроить.

Другой пример -hudson.tasks.BuildWrapper - это позволяет нам определять действия до и после.

У нас также есть дополнительный плагинEmail Extension, который определяет точку расширенияhudson.plugins.emailext.plugins.RecipientProvider, которая позволяет указывать получателей электронной почты. Пример реализации доступен здесь:hudson.plugins.emailext.plugins.recipients.UpstreamComitterRecipientProvider.

Примечание: существует устаревший подход, когда класс плагина должен расширятьhudson.Plugin. Однакоit’s now recommended to use extension points instead.

3.2. Инициализация плагина

Необходимо рассказать Дженкинсу о нашем расширении и о том, как его создать.

Сначала мы определяем статический внутренний класс внутри плагина и отмечаем его с помощью аннотацииhudson.Extension:

class MyPlugin extends BuildWrapper {
    @Extension
    public static class DescriptorImpl
      extends BuildWrapperDescriptor {

        @Override
        public boolean isApplicable(AbstractProject item) {
            return true;
        }

        @Override
        public String getDisplayName() {
            return "name to show in UI";
        }
    }
}

Во-вторых, нам нужно определить конструктор, который будет использоваться для создания экземпляра объекта плагина, и пометить его аннотациейorg.kohsuke.stapler.DataBoundConstructor.

Для этого можно использовать параметры. Они отображаются в пользовательском интерфейсе и автоматически доставляются Дженкинсом.

E.g. рассмотримMaven plugin:

@DataBoundConstructor
public Maven(
  String targets,
  String name,
  String pom,
  String properties,
  String jvmOptions,
  boolean usePrivateRepository,
  SettingsProvider settings,
  GlobalSettingsProvider globalSettings,
  boolean injectBuildVariables) { ... }

Он сопоставлен со следующим пользовательским интерфейсом:

image

Также возможно использовать аннотациюorg.kohsuke.stapler.DataBoundSetter с сеттерами.

4. Реализация плагина

Мы намерены собирать базовую статистику проекта во время сборки, поэтомуhudson.tasks.BuildWrapper - это правильный путь.

Давайте реализуем это:

class ProjectStatsBuildWrapper extends BuildWrapper {

    @DataBoundConstructor
    public ProjectStatsBuildWrapper() {}

    @Override
    public Environment setUp(
      AbstractBuild build,
      Launcher launcher,
      BuildListener listener) {}

    @Extension
    public static class DescriptorImpl extends BuildWrapperDescriptor {

        @Override
        public boolean isApplicable(AbstractProject item) {
            return true;
        }

        @Nonnull
        @Override
        public String getDisplayName() {
            return "Construct project stats during build";
        }

    }
}

Хорошо, теперь нам нужно реализовать реальную функциональность.

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

class ProjectStats {

    private int classesNumber;
    private int linesNumber;

    // standard constructors/getters
}

И напишите код, который строит данные:

private ProjectStats buildStats(FilePath root)
  throws IOException, InterruptedException {

    int classesNumber = 0;
    int linesNumber = 0;
    Stack toProcess = new Stack<>();
    toProcess.push(root);
    while (!toProcess.isEmpty()) {
        FilePath path = toProcess.pop();
        if (path.isDirectory()) {
            toProcess.addAll(path.list());
        } else if (path.getName().endsWith(".java")) {
            classesNumber++;
            linesNumber += countLines(path);
        }
    }
    return new ProjectStats(classesNumber, linesNumber);
}

Наконец, нам нужно показать статистику конечным пользователям. Давайте создадим для этого HTML-шаблон:




    
    $PROJECT_NAME$


Project $PROJECT_NAME$:
Classes number Lines number
$CLASSES_NUMBER$ $LINES_NUMBER$

И заполните его во время сборки:

public class ProjectStatsBuildWrapper extends BuildWrapper {
    @Override
    public Environment setUp(
      AbstractBuild build,
      Launcher launcher,
      BuildListener listener) {
        return new Environment() {

            @Override
            public boolean tearDown(
              AbstractBuild build, BuildListener listener)
              throws IOException, InterruptedException {

                ProjectStats stats = buildStats(build.getWorkspace());
                String report = generateReport(
                  build.getProject().getDisplayName(),
                  stats);
                File artifactsDir = build.getArtifactsDir();
                String path = artifactsDir.getCanonicalPath() + REPORT_TEMPLATE_PATH;
                File reportFile = new File("path");
                // write report's text to the report's file
            }
        };
    }
}

5. использование

Пришло время объединить все, что мы создали, и увидеть это в действии.

Предполагается, что Jenkins запущен и работает в локальной среде. В противном случае обратитесь кinstallation details.

5.1. Добавить плагин в Jenkins

Теперь давайте создадим наш плагин:

mvn install

Это создаст файл*.hpi в каталогеtarget. Нам нужно скопировать его в каталог плагинов Jenkins (по умолчанию~/.jenkins/plugin):

cp ./target/jenkins-hello-world.hpi ~/.jenkins/plugins/

Наконец, давайте перезапустим сервер и убедитесь, что плагин применен:

  1. Откройте панель CI вhttp://localhost:8080

  2. Перейдите кManage Jenkins | Manage Plugins | Installed

  3. Найдите наш плагин

image

5.2. Настроить задание Jenkins

Давайте создадим новое задание для проекта Apache commons-lang с открытым исходным кодом и настроим там путь к его репозиторию Git:

image

Мы также должны включить наш плагин для этого:

image

5.3. Проверьте результаты

Все готово, давайте посмотрим, как это работает.

Мы можем построить проект и перейти к результатам. Мы видим, что файлstats.html доступен здесь:

image

Откроем:

image

Это то, что мы ожидали - один класс с тремя строками кода.

6. Заключение

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

Естественно, мы не охватили все аспекты разработки расширений CI, мы просто предоставили базовый обзор, идеи дизайна и начальную настройку.

И, как всегда, исходный код можно найтиover on GitHub.