Написание плагин Дженкинс
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) { ... }
Он сопоставлен со следующим пользовательским интерфейсом:
Также возможно использовать аннотацию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/
Наконец, давайте перезапустим сервер и убедитесь, что плагин применен:
-
Откройте панель CI вhttp://localhost:8080
-
Перейдите кManage Jenkins | Manage Plugins | Installed
-
Найдите наш плагин
5.2. Настроить задание Jenkins
Давайте создадим новое задание для проекта Apache commons-lang с открытым исходным кодом и настроим там путь к его репозиторию Git:
Мы также должны включить наш плагин для этого:
6. Заключение
В этом руководстве мы создали плагинJenkins с нуля и убедились, что он работает.
Естественно, мы не охватили все аспекты разработки расширений CI, мы просто предоставили базовый обзор, идеи дизайна и начальную настройку.
И, как всегда, исходный код можно найтиover on GitHub.