Так, вы уже какое-то время используете 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("Для задачи
