Так, вы уже какое-то время используете Jenkins, кликаете по его веб-интерфейсу, настраиваете пайплайны и иногда ругаетесь, когда что-то ломается в 3 часа ночи. Но потом однажды вы думаете: «Хотелось бы, чтобы Jenkins мог делать вот это конкретное дело». Вы ищете плагин, пролистываете сотни результатов, и ничего не подходит под ваши нужды. Ну что ж, друг мой, пришло время закатать рукава и построить свой собственный плагин.
Не волнуйтесь — разработка плагинов для Jenkins с помощью Groovy не так страшна, как отладка инцидента в продакшене без логов. На самом деле, это удивительно просто, как только вы поймёте основы. И в качестве бонуса: у вас наконец-то будет что-то впечатляющее, чтобы показать на следующем стендапе команды.
Это руководство поможет вам пройти путь от «Я никогда не писал плагин для Jenkins» до «Я только что выпустил свой первый работающий плагин» с практическими примерами, реальным кодом и без всяких расплывчатых фраз типа «детали реализации оставлены в качестве упражнения для читателя».
Почему Groovy для разработки плагинов Jenkins
Прежде чем мы углубимся в код, давайте ответим на главный вопрос: почему Groovy? Jenkins сам по себе написан на Java, но Groovy привносит что-то особенное. Это язык JVM, который ощущается как скриптовый, но имеет мощь Java под капотом. Вы получаете динамическую типизацию, замыкания и синтаксис, который не заставляет вас хотеть швырнуть клавиатуру через комнату.
Groovy запускается непосредственно внутри JVM Jenkins, предоставляя вам доступ ко всем внутренним объектам и API Jenkins. Это значит, что ваш плагин может взаимодействовать с Jenkins на глубоком уровне — манипулировать сборками, получать доступ к конфигурациям и интегрироваться с другими плагинами без проблем. К тому же, если вы раньше писали Jenkins пайплайны, вы уже знакомы с синтаксисом Groovy.
Предварительные требования: что вам нужно перед началом
Позвольте мне спасти вас от классической ловушки разработчиков — броситься с головой и наткнуться на стену через два часа. Вот что вам на самом деле нужно:
Среда разработки:
- JDK 8 или выше (Jenkins всё ещё любит Java 8, хотя более новые версии тоже подходят)
- Maven 3.6+ (Jenkins плагины используют Maven для управления сборкой)
- Ваша любимая IDE (IntelliJ IDEA или Eclipse отлично подойдут)
- Git (для контроля версий, потому что мы здесь профессионалы)
Требования к знаниям:
- Базовое знание синтаксиса Java/Groovy (вам не нужно быть экспертом, но нужно знать классы и методы)
- Знакомство с концепциями Jenkins (задачи, сборки, узлы)
- Понимание структуры проекта Maven полезно, но не обязательно
Рекомендуемая настройка:
- Локальный экземпляр Jenkins для тестирования (Docker делает это тривиальным)
- Терпение (баги будут, примите их)
Понимание архитектуры плагинов Jenkins
Прежде чем писать код, давайте разберёмся, как на самом деле работают плагины Jenkins. Представьте Jenkins как модульную систему, где плагины — это как кубики LEGO — каждый добавляет определённую функциональность и может взаимодействовать с другими кубиками.
Плагины Jenkins работают через точки расширения — предопределённые интерфейсы, которые плагины могут реализовывать, чтобы вписаться в функциональность Jenkins. Ваш плагин реализует эти точки расширения, и Jenkins автоматически обнаруживает и интегрирует их во время выполнения. Именно поэтому вы можете установить плагин и сразу же увидеть новые функции без перезапуска Jenkins (ну, в большинстве случаев).
Настройка среды разработки
Давайте приступим к делу. Сначала нам нужно настроить структуру проекта Maven. Создайте новую директорию для вашего плагина:
mkdir jenkins-sample-plugin
cd jenkins-sample-plugin
Теперь создайте файл pom.xml
. Это ДНК вашего плагина — он определяет зависимости, конфигурацию сборки и метаданные:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>4.40</version>
<relativePath />
</parent>
<groupId>com.example</groupId>
<artifactId>sample-jenkins-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>hpi</packaging>
<name>Sample Jenkins Plugin</name>
<description>Практический пример разработки плагина для Jenkins</description>
<properties>
<jenkins.version>2.361.1</jenkins.version>
<java.level>8</java.level>
</properties>
<dependencies>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-step-api</artifactId>
<version>2.24</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>repo.jenkins-ci.org</id>
<url>https://repo.jenkins-ci.org/public/</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>repo.jenkins-ci.org</id>
<url>https://repo.jenkins-ci.org/public/</url>
</pluginRepository>
</pluginRepositories>
</project>
Обратите внимание на строку <packaging>hpi</packaging>
? HPI (Hudson Plugin Interface) — это специальный формат плагина Jenkins. Это, по сути, JAR файл с дополнительными метаданными. Jenkins знает, как загружать и выполнять их.
Создание вашего первого плагина: шаг за шагом
Давайте создадим что-нибудь полезное — плагин, который извлекает последний номер сборки из задачи Jenkins. Просто, но это демонстрирует основные концепции, которые вам понадобятся для более сложных плагинов.
Шаг 1: Создание структуры директорий
Maven ожидает определённую структуру директорий. Создайте эти папки:
mkdir -p src/main/java/com/example/jenkins/plugin
mkdir -p src/main/resources/com/example/jenkins/plugin
Шаг 2: Написание класса плагина
Создайте файл src/main/java/com/example/jenkins/plugin/BuildNumberRetriever.java
:
package com.example.jenkins.plugin;
import hudson.Extension;
import hudson.model.AbstractProject;
import hudson.model.Run;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import jenkins.tasks.SimpleBuildStep;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import javax.annotation.Nonnull;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.util.FormValidation;
import java.io.IOException;
public class BuildNumberRetriever extends Builder implements SimpleBuildStep {
private final String jobName;
@DataBoundConstructor
public BuildNumberRetriever(String jobName) {
this.jobName = jobName;
}
public String getJobName() {
return jobName;
}
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher,
BuildListener listener) throws IOException, InterruptedException {
listener.getLogger().println("Извлечение последнего номера сборки для задачи: " + jobName);
AbstractProject<?, ?> project = (AbstractProject<?, ?>)
build.getProject().getParent().getItem(jobName);
if (project == null) {
listener.getLogger().println("Задача не найдена: " + jobName);
return false;
}
Run<?, ?> lastBuild = project.getLastBuild();
if (lastBuild != null) {
int buildNumber = lastBuild.getNumber();
listener.getLogger().println("Последний номер сборки: " + buildNumber);
// Установка как переменной окружения для последующих шагов сборки
build.addAction(new BuildNumberAction(buildNumber));
return true;
} else {
listener.getLogger().println("Для задачи