Jenkinsプラグインの作り方

# はじめに

Jenkinsプラグインの開発方法を述べた日本語のドキュメントが少なかったので書きました。[チュートリアル](https://jenkins.io/doc/developer/tutorial/prepare/)から一歩先に進もうとしたときに必要になるであろう知識をまとめています。2018.04時点の情報です。

# 開発環境を作る

## 必要なツールをインストールする

プラグイン開発にはJDKとmavenが必要です。[プラグイン開発のチュートリアル](https://jenkins.io/doc/developer/tutorial/prepare/)を参考にjdkとmavenをインストールします。

## プロジェクトの雛形を作る

mavenでプロジェクトの雛形を生成します。

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

コマンドを実行すると対話形式で雛形の種類やartifactID(プラグイン名)などいくつか質問されるので適当に答えてください。雛形ができたら以下のコマンドで http://localhost:8080/jenkins にJenkinsが起動することを確認してください。

```sh
mvn hpi:run
```

ここで起動したJenkinsには開発したプラグインが挿さります。このJenkins上でプラグインの動作を確認できます。

# プラグイン開発の基本的な流れ

## 本体クラスを作る

プラグインはJenkinsの[拡張ポイント](https://jenkins.io/doc/developer/extensions/jenkins-core/)を継承した一つのクラスです。プラグインを作るときは、プラグインで拡張する機能に合う拡張ポイントを選び、それを継承したクラスを作ります。そして、拡張ポイントのメソッドをオーバーライドすることで機能を実装していきます。例えばビルド手順の拡張ポイントである[Builder](http://javadoc.jenkins-ci.org/hudson/tasks/Builder.html)を継承したクラスでperformメソッドをオーバーライドすると、新しいビルド手順を追加するプラグインを作ることができます。

[拡張ポイント一覧](https://jenkins.io/doc/developer/extensions/jenkins-core/)は、拡張ポイントごとにそれを継承して作られたプラグインの実例を見ることができるため、拡張ポイント選びと実装に役立ちます。

## プラグインの存在をJenkinsに知らせる

本体クラスを作っただけではJenkinsからプラグインを利用することができません。プラグインを利用するためには、プラグインの存在をJenkinsに知らせなければいけません。そのためには、本体クラスのインナークラスとしてDesctiptorを作り、`@Extention`アノテーションを付ける必要があります。

例えば[AWS CodeBuildプラグインの本体クラス](https://github.com/awslabs/aws-codebuild-jenkins-plugin/blob/9541d89e6ad44bb049c013cd44166eae30192fb1/src/main/java/CodeBuilder.java)を見ると、DescriptorImplクラスがインナークラスとして定義され`@Extension`アノテーションが付けられています。DescriptorImplはBuildStepDescriptorを継承していますが、このようにインナークラスとして定義するDescriptorは、既存のDescriptorを継承しなければいけません。継承するDescriptorは拡張ポイントごとに異なります。どのDescriptorを継承しなければいけないかは[拡張ポイント一覧](https://jenkins.io/doc/developer/extensions/jenkins-core/)の実例から知ることができます。

## プラグインに設定を追加する

プラグインにはグローバルとジョブごとの2種類の設定を持たせることができます。グローバルな設定はJenkinsのトップページの「Jenkinsの管理->システムの設定」に、ジョブごとの設定は各ジョブの「設定」に表示されるものです。それぞれの設定を追加する方法は以下の通りです。

### グローバルな設定を追加する

設定の入力フォームを表示するためにglobal.jellyを作成します。プラグインの本体がsrc/main/java/jenkins/plugins/hoge/fugaに置かれているなら、global.jellyはsrc/main/**resources**/jenkins/plugins/hoge/fugaに置いてください。global.jellyの書き方は既存のプラグインを参考にすると良いです。例えばMyPluginのnameを設定として追加するなら以下のようなglobal.jellyを作ります。

```xml
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">

  <f:section title="${%MyPlugin}" name="MyPlugin">
    <f:entry title="${%name}" field="name">
      <f:textbox />
    </f:entry>
  </f:section>

</j:jelly>
```

次に、入力された設定を読み取れるようにDescriptorに手を加えます。設定はDescriptorのconfigureメソッドにJSONObject型の引数として渡されます。先ほどのglobal.jellyの例なら、以下のようにDescriptorに手を加えればnameを取り出すことができます。

```java
@Extension
public static class DescriptorImpl extends BuildStepDescriptor<Builder> {

    private String name;

    public DescriptorImpl(){
        load(); // 過去に保存したグローバル設定をロードする
    }

    @Override
    public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
        json = json.getJSONObject("MyPlugin"); // json引数にグローバル設定が渡される
        name = json.getString("name");
        save();// グローバル設定を保存する
        return true;
    }

    public String getName(){ // 本体クラスがnameを取り出せるようにgetterを用意する
        return name;
    }
    .
    .
    .
```

本体クラスがグローバル設定を利用するにはDescriptorから値を取り出さなければいけません。そのために、本体クラスに以下のような修正を加えます。

```java
public class MyPlugin extends Builder implements SimpleBuildStep {

    @Override
    public Descriptor getDescriptor() { // Descriptorのgetter
        return (Descriptor) super.getDescriptor();
    }

    @Override
    public void perform(Run<?, ?> run, FilePath workspace, Launcher launcher, TaskListener listener) throws InterruptedException, IOException {
        getDescriptor().getName(); // Descriptorからnameを取得する
    }
    .
    .
    .
}
```

### ジョブごとの設定を追加する

設定の入力フォームを表示するためにconfig.jellyを作成します。グローバル設定を追加するときと同じように、プラグインの本体がsrc/main/java/jenkins/plugins/hoge/fugaに置かれているなら、config.jellyはsrc/main/**resources**/jenkins/plugins/hoge/fugaに置いてください。例えばcolorを設定として追加するなら以下のようなconfig.jellyを作ります。

```xml
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">

    <f:entry title="${%Color}" field="color">
        <f:textbox />
    </f:entry>

</j:jelly>
```

本体クラスでジョブごとの設定を利用するためには、コンストラクタに`@DataBoundConstructor`アノテーションを付け、引数を追加する必要があります。

```java
@DataBoundConstructor
public class MyPlugin extends Builder implements SimpleBuildStep {

    final private String color;

    @DataBoundConstructor
    public MyPlugin(String color){
        this.color = color; // ジョブごとの設定が引数に渡される
    }
    .
    .
    .
}
```

# おわりに

Jenkinsプラグインの基本的な開発方法を説明しました。より実践的なことは[拡張ポイント](https://jenkins.io/doc/developer/extensions/jenkins-core/)に載っている実例から学ぶと良いかと思います。

# 参考情報: プラグイン開発に役立つ公式ドキュメント

|ドキュメント                    |URL                                                      |
|:---                            |:---                                                     |
|Jenkinsの開発者向けドキュメント |https://jenkins.io/doc/developer/                        |
|プラグイン作成チュートリアル    |https://jenkins.io/doc/developer/tutorial/               |
|プラグインで拡張できる機能一覧  |https://jenkins.io/doc/developer/extensions/jenkins-core/|
|Jenkinsの開発者向けwiki         |https://wiki.jenkins.io/display/JENKINS/Extend+Jenkins   |
|JenkinsのJavaDoc                |http://javadoc.jenkins-ci.org/                           |