Так, вы уже какое-то время используете 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 — каждый добавляет определённую функциональность и может взаимодействовать с другими кубиками.

graph TB A[Jenkins Core] --> B[Plugin Manager] B --> C[Ваш плагин] B --> D[Git Plugin] B --> E[Pipeline Plugin] C --> F[Extension Points] D --> F E --> F F --> G[Jenkins API] G --> A

Плагины 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("Для задачи