扩展你的插件

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

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

  • 不单单是在构建日志中记录问候语中使用的名称,还要有适当的数据结构。

  • 在构建中添加一个新页面,用来显示问候语中使用的名称。

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

记录名称

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

package io.jenkins.plugins.sample;

import hudson.model.Action;

public class HelloWorldAction implements Action {

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

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

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

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

(...)

public class HelloWorldAction implements Action {

    private String name;

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

    public String getName() {
        return name;
    }

    (...)

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

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

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

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

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

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

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

添加一个显示名称的视图

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

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

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

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

    @Override
    public String getUrlName() {
        return "greeting"; (3)
    }
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 输出。 它有许多有用的功能:它

  • 支持条件和循环

  • 允许包含在其他地方定义的 view fragments

  • 可用于定义可重用的UI组件

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 上。 添加以下内容:

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout" xmlns:st="jelly:stapler">
    <l:layout title="Greeting"> (1)
        <l:main-panel> (2)
            <h1> (3)
                Name: ${it.name} (4)
            </h1>
        </l:main-panel>
    </l:layout>
</j:jelly>
1 layout 是 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) 加载操作和运行时分别调用该方法。

(...)
import hudson.model.Run;
import jenkins.model.RunAction2;

public class HelloWorldAction implements RunAction2 { (1)

    private transient Run run; (2)

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

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

    public Run getRun() { (5)
        return run;
    }
(...)
1 实现 RunAction2 接口,以便添加到 Run 的 Action 能够恰当地引用 Run。
2 Run 存储在一个瞬态动作中,所以这个字段不会被序列化到磁盘上。
3 首次将此操作附加到 Run 时设置该字段。
4 从磁盘加载此操作时设置该字段。
5 这将使 Run 可用于 Jelly 视图 — 它不能访问私有字段。

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

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

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

view2

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

故障排除

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