扩展你的插件

尽管可以使用纯文本编辑器,但使用支持 Maven 的 Java IDE 更容易进行接下来的步骤。

到用新功能扩展示例插件的时候了:

我们必须存储运行构建时使用的名称,而不是使用配置中的名称,因为配置随时可能会被更改。

记录名称

首先,在与 HelloWorldBuilder 相同的包中创建一个名为 HelloWorldAction 的类。 该类需要实现 ActionActions 是 Jenkins 中可扩展性的基本构建块:它们可以附加到许多模型对象上并被储存起来,并可以添加到它们的 UI 中。

  1. package io.jenkins.plugins.sample;
  2. import hudson.model.Action;
  3. public class HelloWorldAction implements Action {
  4. @Override
  5. public String getIconFileName() {
  6. return null;
  7. }
  8. @Override
  9. public String getDisplayName() {
  10. return null;
  11. }
  12. @Override
  13. public String getUrlName() {
  14. return null;
  15. }
  16. }

由于我们要存储问候中使用的名称,因此我们需要为此类添加一个 getter。我们还将添加一个以 name 为参数的构造方法。

  1. (...)
  2. public class HelloWorldAction implements Action {
  3. private String name;
  4. public HelloWorldAction(String name) {
  5. this.name = name;
  6. }
  7. public String getName() {
  8. return name;
  9. }
  10. (...)

现在,我们实际上需要在构建步骤执行时创建该类的一个实例。 我们需要扩展 HelloWorldBuilder 类中的 perform 方法来把我们创建的动作的一个实例添加到正在运行的构建中:

  1. (...)
  2. @Override
  3. public void perform(Run<?, ?> run, FilePath workspace, Launcher launcher, TaskListener listener) throws InterruptedException, IOException {
  4. run.addAction(new HelloWorldAction(name)); (1)
  5. if (useFrench) {
  6. listener.getLogger().println("Bonjour, " + name + "!");
  7. } else {
  8. listener.getLogger().println("Hello, " + name + "!");
  9. }
  10. }
  11. (...)
1这一行是新增的,其他所有内容都保持不变。

保存这些修改,然后用 mvn hpi:run 再次运行插件。

每当修改 Java 源代码或添加删除资源文件时,您都需要重新启动 Jenkins 实例才能使修改生效。 Jenkins 通过 hpi:run 运行时只能编辑一些资源文件。

现在,使用此构建步骤运行构建时,该操作将被添加到构建数据中。 我们可以通过查看与 work/jobs/JOBNAME/builds/BUILDNUMBER/ 目录中的构建版本相对应的 build.xml 文件来确认。

我们可以看到,这个构建中有两个动作:

  1. <build>
  2. <actions>
  3. <hudson.model.CauseAction> (1)
  4. <causes>
  5. <hudson.model.Cause_-UserIdCause/>
  6. </causes>
  7. </hudson.model.CauseAction>
  8. <io.jenkins.plugins.sample.HelloWorldAction plugin="demo@1.0-SNAPSHOT"> (2)
  9. <name>Jenkins</name> (3)
  10. </io.jenkins.plugins.sample.HelloWorldAction>
  11. </actions>
  12. (...)
  13. </build>
1构建原因(构建如何触发)也存储为 Action。在这种情况下,匿名用户开始了构建。
2这是我们创建的 Action。
3这是创建构建时使用的名称。

添加一个显示名称的视图

接下来,我们需要使我们正在存储的构建可视化。

首先,我们需要回到过去 HelloWorldAction 并定义图标,标题和 URL 名称:

  1. @Override
  2. public String getIconFileName() {
  3. return "document.png"; (1)
  4. }
  5. @Override
  6. public String getDisplayName() {
  7. return "Greeting"; (2)
  8. }
  9. @Override
  10. public String getUrlName() {
  11. return "greeting"; (3)
  12. }
1这是用于侧面板项目的图标。 document.png 是 Jenkins 绑定的预定义图标之一。
2这是用于侧面板项目的标签。
3这是用于此操作的 URL 片段。

通过这些修改,操作将显示在构建的侧面板中,并链接到 URL http://JENKINS/job/JOBNAME/BUILDNUMBER/greeting/

sidepanel item

接下来,需要定义出现在该 URL 上的页面。为了在 Jenkins 创建这样的 视图 , 通常使用 Apache Commons Jelly。Jelly 允许用 XML 定义 XML 和 XHTML 输出。它有许多有用的功能:它

src/main/resources/io/jenkins/plugins/sample/ 中, 我们需要创建一个新的名为 HelloWorldAction/ 的目录。该目录与 HelloWorldAction 类对应并包含相关资源。

它是 src/main/resources 中的一个目录, 而不是 src/main/java
我们可以看到与构建步骤 HelloWorldBuilder 相关的资源,它被存储在 src/main/resources/io/jenkins/plugins/sample/HelloWorldBuilder/ 目录。config.jelly 是构建步骤配置表单,包含构建步骤配置的本地化的各种 config.properties 文件和为配置提供了本地化的内联帮助 help.html 文件。

src/main/resources/io/jenkins/plugins/sample/HelloWorldAction/ 目录创建名为 index.jelly 的文件。这将会显示在 http://JENKINS/job/JOBNAME/BUILDNUMBER/greeting/ URL 上。添加以下内容:

  1. <?jelly escape-by-default='true'?>
  2. <j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout" xmlns:st="jelly:stapler">
  3. <l:layout title="Greeting"> (1)
  4. <l:main-panel> (2)
  5. <h1> (3)
  6. Name: ${it.name} (4)
  7. </h1>
  8. </l:main-panel>
  9. </l:layout>
  10. </j:jelly>
1layout 是 Jenkins 核心中定义的可重用 tag,它提供了页眉,侧面板,主要内容区域和页脚的基本页面布局。
2为了使名称显示在主内容区域(而不是侧面板),我们需要将输出包裹在 main-panel 标签中。
3我们可以使用任何 HTML 标签,并将它们用于输出。
4这是一个 JEXL 表达式。 it 引用视图所属的Java对象 (类似于Java中的 this),在本例中为 HelloWorldAction 实例。 it.name 等同于调用 getName()

结果页面看起来像这样:

view1

将构建的侧面板添加到视图

在上面的输出中没有侧面板,由于此视图与特定版本相关,因此应该显示该版本的侧面板。 为此,我们首先需要获取对我们动作中相应构建的引用,然后在动作视图中包含构建的侧面视图 fragment

为了获得 HelloWorldAction 所属的构建(或者更一般地说, Run)的引用,我们需要改变现有的类以使其实现 RunAction2

该接口添加了两个方法,当运行首次连接到构建时 onAttached(Run),以及从磁盘 onLoad(Run) 加载操作和运行时分别调用该方法。

  1. (...)
  2. import hudson.model.Run;
  3. import jenkins.model.RunAction2;
  4. public class HelloWorldAction implements RunAction2 { (1)
  5. private transient Run run; (2)
  6. @Override
  7. public void onAttached(Run<?, ?> run) {
  8. this.run = run; (3)
  9. }
  10. @Override
  11. public void onLoad(Run<?, ?> run) {
  12. this.run = run; (4)
  13. }
  14. public Run getRun() { (5)
  15. return run;
  16. }
  17. (...)
1实现 RunAction2 接口,以便添加到 Run 的 Action 能够恰当地引用 Run。
2Run 存储在一个瞬态动作中,所以这个字段不会被序列化到磁盘上。
3首次将此操作附加到 Run 时设置该字段。
4从磁盘加载此操作时设置该字段。
5这将使 Run 可用于 Jelly 视图 — 它不能访问私有字段。

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

  1. (...)
  2. <l:layout title="Greeting">
  3. <l:side-panel> (1)
  4. <st:include page="sidepanel.jelly" it="${it.run}" optional="true" /> (2)
  5. </l:side-panel>
  6. <l:main-panel>
  7. (...)
  8. </l:main-panel>
  9. </l:layout>
  10. (...)
1main-panel 类似,我们希望内容仅在侧面板中显示,因此我们需要将它们包裹在此元素中。
2在这个位置 includes 另一个对象 Run 的视图片段 sidepanel.jelly 。 我们把它标记为可选的,所以如果这个视图片断不存在,就不会显示错误,因为抽象类 Run 没有定义这样的视图,只有它的子类 AbstractBuild

有了这些修改,我们创建的视图与 Jenkins UI 正确集成,与构建版本相关的内置页面没有任何区别:

view2

恭喜,您已成功创建并大幅扩展了 Jenkins 插件!

故障排除

没有适合你的东西? 在 IRCjenkinsci-dev 邮件列表中寻求帮助。