Jenkins插件开发官方Demo

准备工作

因为Jenkins是基于 Java开发的,插件开发首先需要安装JDKMaven

Maven安装完成后,需要修改配置文件,内容如下

<settings>
    <pluginGroups>
        <pluginGroup>org.jenkins-ci.tools</pluginGroup>
    </pluginGroups>

    <profiles>
        <!-- Give access to Jenkins plugins -->
        <profile>
            <id>jenkins</id>
            <activation>
                <activeByDefault>true</activeByDefault> <!-- change this to false, if you don't like to have it on per default -->
            </activation>
            <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>
        </profile>
    </profiles>
    <mirrors>
        <mirror>
            <id>repo.jenkins-ci.org</id>
            <url>https://repo.jenkins-ci.org/public/</url>
            <mirrorOf>m.g.o-public</mirrorOf>
        </mirror>
    </mirrors>
</settings>

插件工程初始化

1、创建插件

找一个目录存放你想要方式Jenkins插件源码的目录,在此处打开命令行终端,键人如下命令,这条命令可以让我们使用与Jenkins相关的原型项目中的一个并生成

mvn -U archetype:generate -Dfilter=io.jenkins.archetypes:

执行日志如下,根据提示依次输入选择原型,版本,输出文件名称,版本号,确认

[INFO] Scanning for projects...
Downloading 。。。。。。
[INFO]
[INFO] ------------------< org.apache.maven:standalone-pom >-------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:3.2.0:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:3.2.0:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO]
[INFO]
[INFO] --- maven-archetype-plugin:3.2.0:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Interactive mode
[WARNING] No archetype found in remote catalog. Defaulting to internal catalog
[INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
Choose archetype:
1: local -> io.jenkins.archetypes:empty-plugin (Skeleton of a Jenkins plugin with a POM and an empty source tree.)
2: local -> io.jenkins.archetypes:global-configuration-plugin (Skeleton of a Jenkins plugin with a POM and an example piece of global configuration.)
3: local -> io.jenkins.archetypes:global-shared-library (Uses the Jenkins Pipeline Unit mock library to test the usage of a Global Shared Library)
4: local -> io.jenkins.archetypes:hello-world-plugin (Skeleton of a Jenkins plugin with a POM and an example build step.)
5: local -> io.jenkins.archetypes:scripted-pipeline (Uses the Jenkins Pipeline Unit mock library to test the logic inside a Pipeline script.)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 4
Choose io.jenkins.archetypes:hello-world-plugin version:
1: 1.1
2: 1.2
3: 1.3
4: 1.4
5: 1.5
6: 1.6
7: 1.7
8: 1.8
9: 1.9
10: 1.10
Choose a number: 10: 10
Downloading 。。。。。
[INFO] Using property: groupId = unused
Define value for property 'artifactId': JenkinsPluginDemo
Define value for property 'version' 1.0-SNAPSHOT: : 1.0
[INFO] Using property: package = io.jenkins.plugins.sample
[INFO] Using property: hostOnJenkinsGitHub = true
Confirm properties configuration:
groupId: unused
artifactId: JenkinsPluginDemo
version: 1.0
package: io.jenkins.plugins.sample
hostOnJenkinsGitHub: true
 Y: : y
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: hello-world-plugin:1.10
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: unused
[INFO] Parameter: artifactId, Value: JenkinsPluginDemo
[INFO] Parameter: version, Value: 1.0
[INFO] Parameter: package, Value: io.jenkins.plugins.sample
[INFO] Parameter: packageInPathFormat, Value: io/jenkins/plugins/sample
[INFO] Parameter: version, Value: 1.0
[INFO] Parameter: package, Value: io.jenkins.plugins.sample
[INFO] Parameter: groupId, Value: unused
[INFO] Parameter: hostOnJenkinsGitHub, Value: true
[INFO] Parameter: artifactId, Value: JenkinsPluginDemo
[WARNING] Don't override file D:\IDEA WorkSpace\JenkinsPluginsDemo\JenkinsPluginDemo
[WARNING] Don't override file D:\IDEA WorkSpace\JenkinsPluginsDemo\JenkinsPluginDemo\src\main\resources\io\jenkins\plugins\sample
[WARNING] Don't override file D:\IDEA WorkSpace\JenkinsPluginsDemo\JenkinsPluginDemo\src\main\java\io\jenkins\plugins\sample
[WARNING] Don't override file D:\IDEA WorkSpace\JenkinsPluginsDemo\JenkinsPluginDemo\src\test\java\io\jenkins\plugins\sample
[INFO] Executing META-INF/archetype-post-generate.groovy post-generation script
[version:1.0, package:io.jenkins.plugins.sample, groupId:unused, hostOnJenkinsGitHub:true, artifactId:JenkinsPluginDemo]
[INFO] Project created from Archetype in dir: D:\IDEA WorkSpace\JenkinsPluginsDemo\JenkinsPluginDemo
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  16:53 min
[INFO] Finished at: 2021-10-14T15:23:42+08:00
[INFO] ------------------------------------------------------------------------

正常执行后,当前目录下方会生成名称为刚才输入的artifactId的名称相同的目录,其中添加了工作插件的基本结构。

进入该目录,执行如下Maven的命令

mvn verify

这一操作会校验是否构建成功,如果需要的一些依赖包本地仓库没有,这一步还会下载很多的依赖,然后遍历配置的构建周期,包括静态分析(FindBugs)和测试,直到日志内容出现下面的信息

[INFO] BugInstance size is 0
[INFO] Error size is 0
[INFO] No errors/warnings found
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:05 min
[INFO] Finished at: 2021-10-15T09:54:04+08:00
[INFO] ------------------------------------------------------------------------

使用IDEA打开这个原型工程,等待工程初始化,最后项目结构如下

image-20211015103720447

2、可能出现的问题

Ⅰ、提示远程目录没有这个原型

[INFO]
[INFO] ------------------< org.apache.maven:standalone-pom >-------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:3.2.0:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:3.2.0:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO]
[INFO]
[INFO] --- maven-archetype-plugin:3.2.0:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Interactive mode
[WARNING] No archetype found in remote catalog. Defaulting to internal catalog
[INFO] Your filter doesn't match any archetype, so try again with another value.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.091 s
[INFO] Finished at: 2021-10-14T14:25:17+08:00
[INFO] ------------------------------------------------------------------------

笔者的解决办法为下载archetype-catalog.xml文件放到Maven本地仓库的根目录下方(非安装目录),点击链接下载

Ⅱ、依赖下载失败

日志如下

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:24 h
[INFO] Finished at: 2021-10-14T16:59:06+08:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal on project JenkinsPluginDemo: Could not resolve dependencies for project io.jenkins.plugins:JenkinsPluginDemo:hpi:1.0-SNAPSHOT: Could not transfer artifact org.jenkins-ci.main:jenkins-war:war:2.277.1 from/to repo.jenkins-ci.org (https://repo.jenkins-ci.org/public/): GET request of: org/jenkins-ci/main/jenkins-war/2.277.1/jenkins-war-2.277.1.war from repo.jenkins-ci.org failed: Connection reset -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/DependencyResolutionException

部分依赖下载可能特别缓慢,甚至如日志那样超时失败,笔者没有找到比较好的解决办法,我直接将下载不了的依赖链接复制下来,然后手动用下载工具(如IDM)下载下来后放在本地仓库,然后重新构建。(整个过程持续了一个下午加一个晚上,早上到公司发现下载又卡住了,可能一晚上都没下吧,七七八八又花了一个多小时才下完)

构建并运行插件

Maven HPI Plugin用于构建和打包Jenkins插件。它提供了一种便利的方式来运行一个已经包含了当前插件的Jenkins实例,在插件工程根目录下输入如下命令

mvn hpi:run

这将在 http://localhost:8080/jenkins/ 建立一个 Jenkins 实例。等待以下控制台输出如下内容

hudson.util.Retrier#start: Performed the action check updates server successfully at the attempt #1

这一步也可以通过IDEA可视化窗口来做

image-20211019203515674

这里创建的Jenkins实例主目录为当前插件工程根目录下的work/目录,也就是说后续运行产生的数据都会存放在这个地方

image-20211019202249446

这里我们创建一个自由风格的插件,命名为HelloWorld

image-20211019202350398

找到Build配置选项,输入你想输入的任何内容

image-20211019202608549

然后点击构建可以看到输出日志如下

Started by user unknown or anonymous
Running as SYSTEM
Building in workspace D:\IDEA WorkSpace\JenkinsPluginsDemo\JenkinsPluginDemo\work\workspace\HelloWorld
Hello, 哈喽世界!
Finished: SUCCESS

尝试扩展插件

接下来我们做两个扩展点:

  • 不单单是在构建日志中记录问候语中使用的名称,还要有适当的数据结构。
  • 在构建中添加一个新页面,用来显示问候语中使用的名称。

1、记录名称

我们必须存储运行构建时使用的名称,而不是使用配置中的名称,因为配置随时可能会被更改。和HelloWorldBuilder同级目录新建一个HelloWorldAction类,该类需要实现Action接口。Action下的实现类是Jenkins中可扩展性的基本构建块,他们可以附加到许多模型对象上并被储存起来,并可以添加到他们的UI中。

/**
 * @author PengHuAnZhi
 * @ProjectName JenkinsPluginDemo
 * @Description TODO
 * @time 2021/10/20 10:13
 */
public class HelloWorldAction implements Action {
    @Override
    public String getIconFileName() {
        return null;
    }

    @Override
    public String getDisplayName() {
        return null;
    }

    @Override
    public String getUrlName() {
        return null;
    }
}

因为我们需要存储问候中使用的名称,所以我们需要在这个Action中定义这个变量,并设置它的Getter和以它为参数的构造方法

/**
 * @author PengHuAnZhi
 * @ProjectName JenkinsPluginDemo
 * @Description TODO
 * @time 2021/10/20 10:13
 */
public class HelloWorldAction implements Action {
    private final String name;

    public HelloWorldAction(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
    
	(...)
}

现在我们需要在构建步骤执行的时候创建这个Action的一个实例。这就需要扩展HelloWorldBuilder类中的perform方法来吧我们创建的动作实例添加到正在运行的构建中

(...)
    @Override
    public void perform(Run<?, ?> run, FilePath workspace, Launcher launcher, TaskListener listener) throws InterruptedException, IOException {
        run.addAction(new HelloWorldAction(name)); 
        if (useFrench) {
            listener.getLogger().println("Bonjour, " + name + "!");
        } else {
            listener.getLogger().println("Hello, " + name + "!");
        }
    }
(...)

现在重启Jenkins(除了静态资源的修改不需要重启,其他都需要重启才能生效),我们也可以通过查看work/jobs/JOBNAME/builds/BUILDNUMBER/下方的build.xml文件来确认:

(...)    
	<actions>
      <hudson.model.CauseAction>
        <causeBag class="linked-hash-map">
          <entry>
            <hudson.model.Cause_-UserIdCause/>
            <int>1</int>
          </entry>
        </causeBag>
      </hudson.model.CauseAction>
      <io.jenkins.plugins.sample.HelloWorldAction plugin="JenkinsPluginDemo@1.0-SNAPSHOT">
        <name>哈喽世界</name>
      </io.jenkins.plugins.sample.HelloWorldAction>
    </actions>
(...)

第一个CauseAction表示的是构建原因(构建如何触发),这种情况下,是匿名用户开始的这次构建

第二个HelloWorldAction便是我们创建的Action,其中的name是我们创建构建的时候使用的名称

2、添加一个显示名称的视图

第一步我们将名称存储下来了,现在我们让他在页面上面显示出来,也就是将我们正在存储的构建可视化

首先,我们回到HelloWorldAction类,并定义图标,标题和URL名称,如下:

@Override
public String getIconFileName() {
    return "document.png";
}

@Override
public String getDisplayName() {
    return "Greeting";
}

@Override
public String getUrlName() {
    return "greeting";
}

其中:

  • document.png用于侧面板项目的图标,这是一个Jenkins绑定的预定义图标之一
  • Greeting用于侧面板项目的标签
  • greeting用于此操作的URL片段

操作完毕后,重启Jenkins,就会发现在http://JENKINS/job/JOBNAME/BUILDNUMBER/页面中出现一个新的Greeting小图标

image-20211020104949161

点开这个页面会链接到URL http://JENKINS/job/JOBNAME/BUILDNUMBER/greeting/,不过现在还没有定义这个页面,所以是一个404的页面

image-20211020105150368

所以接下来需要定义出现在这个URL上的页面。为了在Jenkins创建这样的视图,通常使用Apache Commons JellyJelly 允许用 XML 定义 XMLXHTML 输出,它支持如下功能

  • 支持条件和循环
  • 允许包含在其他地方定义的 view fragments
  • 可用于定义可重用的UI组件

现在我们在src/main/resources/io/jenkins/plugins/sample/目录下创建一个新的名为HelloWorldAction的目录,如你所见,这个目录是和HelloWorldAction对应的,它可以放置与其相关的资源。

我们可以观察到src/main/resources/io/jenkins/plugins/sample/HelloWorldAction/目录便是HelloWorldBuilder相关的资源目录,其中config.jelly便是构建步骤配置表单,包含构建步骤配置的本地化的各种config*.properties文件和为配置提供了 本地化的内联帮助help*.html文件

在刚在创建的目录HelloWorldAction下创建名为index.jelly的文件,这将会显示在http://JENKINS/job/JOBNAME/BUILDNUMBER/greeting/ 链接下,添加如下内容

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout" xmlns:st="jelly:stapler">
    <l:layout title="Greeting"> 
        <l:main-panel> 
            <h1> 
                Name: ${it.name} 
            </h1>
        </l:main-panel>
    </l:layout>
</j:jelly>

其中:

  • layoutJenkins核心中定义的可重用tag,它提供了页眉,侧面板,主要内容区域和页脚的基本页面布局。
  • 为了使名称显示在内容区域(非侧面板),我们需要将输出包裹在main-panel标签中。
  • main-panel标签内部我们可以使用任何HTML标签,并将它们用于输出。
  • Name:${it.name}是一个JEXL表达式,引用视图所属的Java对象(类似Java中的this),在本例中为HelloWorldAction实例。it.name等同于调用getName()

最后 重启Jenkins观察

image-20211020113725551

由于这个界面和对应构建的版本相关,如图

image-20211020114048038

所以我们需要显示对应版本的侧面板,为此我们首先需要获取的就是对我们动作中响应构建的引用,然后在动作视图中包含构建构建的侧面视图fragment,为了获取HelloWorldAction所属的构建(Run)引用,我们需要改变现有的类,Jenkins为我们提供了新的接口RunAction2,这个接口相较于Action接口新增了两个方法:

  • onAttached(Run):首次连接到构建回调方法
  • onLoad(Run):从磁盘加载操作和运行分别都会调用的方法
/**
 * @author PengHuAnZhi
 * @ProjectName JenkinsPluginDemo
 * @Description TODO
 * @time 2021/10/20 10:13
 */
public class HelloWorldAction implements RunAction2 {
    private final String name;
    private transient Run<?, ?> run;

    public HelloWorldAction(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String getIconFileName() {
        return "document.png";
    }

    @Override
    public String getDisplayName() {
        return "Greeting";
    }

    @Override
    public String getUrlName() {
        return "greeting";
    }

    @Override
    public void onAttached(Run<?, ?> run) {
        this.run = run;
    }

    @Override
    public void onLoad(Run<?, ?> run) {
        this.run = run;
    }

    public Run<?, ?> getRun() {
        return run;
    }
}

这些一旦完成之后,我们需要将扩展这个视图来将 Run 的侧面板视图片段包含进来:

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout" xmlns:st="jelly:stapler">
    <l:layout title="Greeting">
        <l:side-panel>
            <st:include page="sidepanel.jelly" it="${it.run}" optional="true" />
        </l:side-panel>
        <l:main-panel>
            <h1>
                Name: ${it.name}
            </h1>
        </l:main-panel>
    </l:layout>
</j:jelly>

main-panel 类似,我们希望内容仅在侧面板中显示,因此我们需要将它们包裹在元素side-panel中。

includes另一个对象 Run 的视图片段 sidepanel.jelly 。 我们把它标记为可选的,所以如果这个视图片断不存在,就不会显示错误,因为抽象类 Run 没有定义这样的视图,只有它的子类 AbstractBuild

至此,官方Demo扩展已经完成,重启检查一下

image-20211020115349326

参考1

参考2

参考3

参考4

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值