Перейти к основному содержимому

Написание плагина Jenkins

· 5 мин. чтения

1. Обзор

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

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

2. Настройка

Первое, что нужно сделать, это настроить проект. К счастью, Jenkins предоставляет для этого удобные архетипы Maven .

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

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 определяет точку расширения hudson.tasks.BuildStep , поэтому мы можем реализовать ее, чтобы обеспечить настраиваемый шаг, который можно настроить.

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

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

Примечание. Существует устаревший подход, при котором класс плагина должен расширять hudson.Plugin . Однако вместо этого теперь рекомендуется использовать точки расширения.

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 .

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

Например, рассмотрим плагин Maven :

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

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

./604911c4b570e9136be01aa6331bd7e1.png

Также можно использовать аннотацию 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<FilePath> 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-шаблон:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>$PROJECT_NAME$</title>
</head>
<body>
Project $PROJECT_NAME$:
<table border="1">
<tr>
<th>Classes number</th>
<th>Lines number</th>
</tr>
<tr>
<td>$CLASSES_NUMBER$</td>
<td>$LINES_NUMBER$</td>
</tr>
</table>
</body>
</html>

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

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 запущен и работает в локальной среде. В противном случае см . подробности установки .

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

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

mvn install

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

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

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

  1. Откройте панель управления CI по адресу http://localhost:8080.
  2. Перейдите к управлению Дженкинсом | Управление плагинами | Установлены
  3. Найдите наш плагин

./9d9795a4bd187db8bba0c471d7548c03.png

5.2. Настройка задания Дженкинса

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

./8b32bbb45b0c5674510b8436549de862.png

Для этого нам также нужно включить наш плагин:

./96bbe2cc631a49e6e9a011df5c694626.png

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

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

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

./2b0cfe28e93743252c1cc48e13e98669.png

Давайте откроем его:

./df33547198cd3e817b33c183ada18b04.png

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

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

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

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

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