第十三章:插件开发
开发自定义 Jenkins 插件,包括项目结构、API 使用、打包部署。
最后更新: 2024-01-15
页面目录
Jenkins 插件开发
本章介绍如何开发自定义 Jenkins 插件,满足特定业务需求。
插件开发概述
为什么开发插件?
- 扩展 Jenkins 核心功能
- 集成内部系统
- 自动化特定工作流程
- 提供定制化体验
插件架构
┌─────────────────────────────────────────────────────────────────┐
│ Jenkins 插件架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Jenkins Core │
│ ├── Extension Points (接口定义) │
│ ├── Plugin Manager │
│ └── Security │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Plugin │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ Extension Point Implementation │ │ │
│ │ │ • Builder (构建步骤) │ │ │
│ │ │ • Publisher (后处理) │ │ │
│ │ │ • Trigger (触发器) │ │ │
│ │ │ • SCM (源码管理) │ │ │
│ │ │ • NodeProperty (节点属性) │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ Jelly/Groovy Views │ │ │
│ │ │ • config.jelly (配置页面) │ │ │
│ │ │ • global.jelly (全局配置) │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
开发环境准备
1. 安装依赖
# Ubuntu/Debian
sudo apt update
sudo apt install -y openjdk-11-jdk maven git
# macOS
brew install openjdk@11 maven
2. JDK 版本要求
| Jenkins 版本 | JDK 要求 |
|---|---|
| Jenkins 2.357+ | JDK 8, JDK 11 |
| Jenkins 2.426.1+ | JDK 11, JDK 17 |
| Jenkins 2.463+ | JDK 11, JDK 17, JDK 21 |
3. Maven 配置
<!-- ~/.m2/settings.xml -->
<settings>
<pluginGroups>
<pluginGroup>org.jenkins-ci.tools</pluginGroup>
</pluginGroups>
<profiles>
<profile>
<id>jenkins</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<jenkins.version>2.440.3</jenkins.version>
<java.level>11</java.level>
</properties>
</profile>
</profiles>
</settings>
创建第一个插件
1. 使用 Maven Archetype
# 创建插件项目
mvn -B archetype:generate \
-DgroupId=com.example \
-DartifactId=jenkins-plugin-tutorial \
-DarchetypeGroupId=org.jenkins-ci.tools \
-DarchetypeArtifactId=jenkins-plugin-archetype \
-DarchetypeVersion=1.13
# 或者使用网易源
mvn -B archetype:generate \
-DarchetypeGroupId=org.jenkins-ci.tools \
-DarchetypeArtifactId=jenkins-plugin-archetype \
-DarchetypeVersion=1.13 \
-DgroupId=com.example \
-DartifactId=hello-world-plugin \
-Dpackage=com.example.helloworld \
-Dversion=1.0-SNAPSHOT
2. 项目结构
hello-world-plugin/
├── pom.xml # Maven 配置
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/
│ │ │ └── helloworld/
│ │ │ ├── HelloWorldBuilder.java # 构建器实现
│ │ │ └── HelloWorldBuilder.DescriptorImpl # 描述符
│ │ └── resources/
│ │ └── com/example/
│ │ └── helloworld/
│ │ ├── HelloWorldBuilder/
│ │ │ ├── config.jelly # 配置页面
│ │ │ └── help-name.html
│ │ └── index.jelly # 插件主页
│ └── test/
│ └── java/
│ └── com/example/
│ └── helloworld/
│ └── HelloWorldBuilderTest.java # 单元测试
└── target/ # 编译输出
3. 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.77</version>
</parent>
<groupId>com.example</groupId>
<artifactId>hello-world-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>hpi</packaging>
<name>Hello World Plugin</name>
<description>A simple Hello World plugin for Jenkins</description>
<properties>
<jenkins.version>2.440.3</jenkins.version>
<java.level>11</java.level>
</properties>
<dependencies>
<!-- Jenkins Test Harness -->
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>structs</artifactId>
<version>1.25</version>
</dependency>
<!-- Annotation API -->
<dependency>
<groupId>org.jenkins-ci</groupId>
<artifactId>annotation-indexer</artifactId>
<version>1.17</version>
</dependency>
<!-- Stapler for Jelly views -->
<dependency>
<groupId>org.jenkins-ci</groupId>
<artifactId> Stapler</artifactId>
<version>1.320</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.jenkins-ci.tools</groupId>
<artifactId>maven-hpi-plugin</artifactId>
<version>3.51</version>
<configuration>
<dependencies>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>credentials</artifactId>
<version>2.6.1</version>
</dependency>
</dependencies>
</configuration>
</plugin>
</plugins>
</build>
</project>
开发步骤
1. 定义扩展点实现
// HelloWorldBuilder.java
package com.example.helloworld;
import hudson.Launcher;
import hudson.Extension;
import hudson.model.AbstractProject;
import hudson.model.Descriptor;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.tasks.Builder;
import hudson.tasks.BuildStepDescriptor;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
public class HelloWorldBuilder extends Builder {
// 用户配置的属性
private final String name;
// 数据绑定构造函数
@DataBoundConstructor
public HelloWorldBuilder(String name) {
this.name = name;
}
// Getter 方法
public String getName() {
return name;
}
// 构建步骤实现
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher,
TaskListener listener) {
// 获取输出流
PrintStream logger = listener.getLogger();
// 输出日志
logger.println("Hello, " + name + "!");
logger.println("Build number: " + build.getNumber());
logger.println("Build workspace: " + build.getWorkspace());
// 如果需要返回失败
// return false;
return true;
}
// 描述符实现
@Extension
@Symbol("helloWorld")
public static class DescriptorImpl extends BuildStepDescriptor<Builder> {
// 显示名称
@Override
public String getDisplayName() {
return "Say Hello World";
}
// 判断是否适用于项目类型
@Override
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
return true;
}
}
}
2. 创建配置页面
<!-- src/main/resources/com/example/helloworld/HelloWorldBuilder/config.jelly -->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="Name" field="name">
<f:textbox default="World"/>
</f:entry>
<f:description>
This plugin prints "Hello, World!" to the build log.
</f:description>
</j:jelly>
3. 创建帮助页面
<!-- src/main/resources/com/example/helloworld/HelloWorldBuilder/help-name.html -->
<div>
<p>
Enter the name to greet. This will be printed in the build log.
</p>
<p>
Example: <code>Jenkins User</code>
</p>
</div>
常用扩展点
Builder (构建步骤)
public class MyBuilder extends Builder {
@DataBoundConstructor
public MyBuilder(String param1, boolean param2) {
// ...
}
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher,
TaskListener listener) {
// 实现构建逻辑
return true;
}
@Extension
public static class DescriptorImpl extends BuildStepDescriptor<Builder> {
// ...
}
}
Publisher (后处理步骤)
public class MyPublisher extends Publisher {
@DataBoundConstructor
public MyPublisher(String config) {
// ...
}
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher,
TaskListener listener) {
// 发布构建产物或通知
return true;
}
@Extension
public static class DescriptorImpl extends RecorderDescriptor<Publisher> {
// ...
}
}
Trigger (触发器)
public class MyTrigger extends Trigger<AbstractProject> {
@DataBoundConstructor
public MyTrigger(String cron) {
super(cron);
}
@Override
public void start(AbstractProject project, boolean newInstance) {
// 启动触发器
}
@Extension
public static class DescriptorImpl extends TriggerDescriptor<AbstractProject> {
// ...
}
}
Recorder (构建记录器)
public class MyRecorder extends Recorder {
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher,
TaskListener listener) {
// 记录构建信息
return true;
}
@Extension
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
// ...
}
}
单元测试
创建测试类
// src/test/java/com/example/helloworld/HelloWorldBuilderTest.java
package com.example.helloworld;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.FreeStyleProject;
import hudson.tasks.Builder;
import org.jenkins-ci.plugins.structs.describable.UninstantiatedDescribable;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.BuildWatcher;
import org.jvnet.hudson.test.HudsonTestCase;
import java.io.IOException;
import static org.junit.Assert.*;
public class HelloWorldBuilderTest {
@Rule
public JenkinsRule j = new JenkinsRule();
@Test
public void testConfigRoundtrip() throws Exception {
// 创建项目
FreeStyleProject project = j.createFreeStyleProject();
// 添加构建步骤
project.getBuildersList().add(new HelloWorldBuilder("Test User"));
// 验证配置
j.configRoundtrip(project);
// 验证构建步骤
HelloWorldBuilder builder = project.getBuildersList().get(HelloWorldBuilder.class);
assertEquals("Test User", builder.getName());
}
@Test
public void testBuilderExecution() throws Exception {
// 创建项目
FreeStyleProject project = j.createFreeStyleProject();
project.getBuildersList().add(new HelloWorldBuilder("Jenkins"));
// 运行构建
FreeStyleBuild build = j.buildAndAssertSuccess(project);
// 验证日志输出
j.assertLogContains("Hello, Jenkins!", build);
}
}
运行测试
# 运行所有测试
mvn test
# 运行单个测试
mvn test -Dtest=HelloWorldBuilderTest
# 运行带调试
mvn test -Dtest=HelloWorldBuilderTest -X
# 跳过测试
mvn clean package -DskipTests
打包与部署
打包插件
# 打包为 .hpi 文件
mvn package
# 查看输出
ls -la target/*.hpi
本地安装
# 复制到 Jenkins 插件目录
cp target/hello-world-plugin.hpi /var/lib/jenkins/plugins/
# 重启 Jenkins
sudo systemctl restart jenkins
通过 Web 上传
系统管理 → 插件管理 → 高级
上传插件: 选择 .hpi 文件
国际化 (i18n)
添加翻译文件
# src/main/resources/com/example/helloworld/HelloWorldBuilder.properties
# 默认 (英文)
DisplayName=Say Hello World
Name=Name
Name.help=Enter the name to greet
# 中文翻译
# src/main/resources/com/example/helloworld/HelloWorldBuilder_zh_CN.properties
DisplayName=\u8BF4\u4E16\u754C\u4F60\u597D
Name=\u59D3\u540D
Name.help=\u8F93\u5165\u8981\u6253\u62DB\u547C\u7684\u59D3\u540D
发布到 Jenkins 插件仓库
1. 准备发布
# 更新版本号
mvn versions:set -DnewVersion=1.0
# 确保没有 SNAPSHOT
mvn release:prepare
2. 发布
# 发布到 Maven Central
mvn release:perform
3. Jenkins Wiki 文档
https://wiki.jenkins-ci.org/display/JENKINS/Hello+World+Plugin
常见 API 使用
1. 获取构建信息
// 获取环境变量
EnvVars envVars = build.getEnvironment(listener);
String branch = envVars.get("BRANCH_NAME");
// 获取构建参数
Map<String, String> params = build.getBuildVariables();
// 获取工作区
FilePath workspace = build.getWorkspace();
2. 使用凭据
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
public boolean perform(AbstractBuild build, TaskListener listener) {
// 获取凭据
List<StandardUsernamePasswordCredentials> credentials =
CredentialsProvider.lookupCredentials(
StandardUsernamePasswordCredentials.class,
build.getProject(),
build.getCause(hudson.model.UserIdCause.class)
);
// 使用凭据
String username = credentials.get(0).getUsername();
String password = credentials.get(0).getPassword().getPlainText();
return true;
}
3. 发送通知
// 发送邮件
String recipient = "user@example.com";
String subject = "Build " + build.getNumber() + " completed";
String body = "Build result: " + build.getResult();
try {
Mailer.descriptor().sendMail(
recipient, subject, body,
build.getEnvironment().get("DEFAULT_RECIPIENTS")
);
} catch (Exception e) {
listener.getLogger().println("Failed to send email: " + e.getMessage());
}
下一步
恭喜您完成了 Jenkins 权威教程的全部内容!
👉 返回教程首页
📚 继续学习: