Jenkinsプラグイン開発ノウハウシリーズ
はじめに
Jenkinsプラグイン開発ノウハウ1 - 環境構築で開発環境は整ったので実際に開発する際のポイントをサンプルコードをもとに紹介したいと思います。
1. サンプルコード
まずサンプルプラグインのJobCreateBuilder.java
の仕様を紹介します。
JobCreateBuilderは実行時に渡された設定された文字列のジョブを作成します。またJenkinsの設定でPrefixを設定しておくと、ジョブ名の前にプレフィックスを追加してジョブを作成します。
例えばJenkinsの設定のPrefixにpre-
、JobCreateBuilderのジョブにはjob01
と設定してジョブを実行するとpre-job01
というジョブが作成されます。またすでに存在するジョブを作成しようとすると失敗します。
1.1. ジョブ開始時に起動されるメソッド
次にJobCreateBuilder
のメインシーケンスを表すメソッドのソースコードを紹介します。
@Override
public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener)
throws InterruptedException, IOException {
// 実行時に指定された新規に作成するジョブ名を取得
String newJobName = getCreateJobNameFromParam(build.getEnvironment(listener));
// ジョブ名が空だったらエラー
if (newJobName.isEmpty()) {
listener.getLogger().println("Error : " + Messages.JobCreateBuilder_JobName_Empty());
return false;
}
try {
// PrefixをJenkinsの設定から取得して、先ほど取得したジョブ名と連結してジョブを作成
Jenkins.getInstance().createProject(FreeStyleProject.class, getDescriptor().getPrefix() + newJobName);
}catch (IllegalArgumentException e) {
// 既に存在するジョブ名だったら場合はジョブの実行を失敗とする
listener.getLogger().println("Error : " + e.getMessage());
return false;
}
// ジョブの作成が成功したことをJenkinsのコンソールにログとして出力
listener.getLogger().println(Messages.JobCreateBuilder_Success() + " name : " + newJobName);
// ジョブの実行が成功
return true;
}
上記のソースコードをを読むとなんとなく実施している処理のイメージがつくと思いますが、簡単にJenkinsAPIのルールと共に説明します。
- ジョブ開始時に起動されるメソッドは
perform
メソッドになります。 - performメソッドは
true
を返すとジョブの実行が成功、false
を返すと失敗となる。 -
Jenkins.getInstance()
からJenkisのインスタンスを取得することで多くの操作が可能となる。
今回はプロジェクトを作成するためのcreateProject
メソッドを実行しています。
詳しくはJenkins公式ドキュメントのJenkinsクラスを確認してみてください。 -
listener.getLogger().println()
でJenkinsの実行時のコンソールログとして出力可能。
続いて設定や画面に関する処理について、ソースコード、設定ファイル、JenkinsのUIを確認しながら説明していきたいと思います。
1.2. すべてのジョブに影響するパラメータ
global.jelly
を作成しておくと、すべてのジョブに影響するパラメータを用意することが可能になります。
今回で言うとPrefixです。
<?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:section title="JobCreate Builder">
<f:entry title="${%Prefix}" field="prefix" description="Prefix of job name created by JobCreate-Builder" default="">
<f:textbox default="" />
</f:entry>
</f:section>
</j:jelly>
今回重要なのは、<f:entry
で始まる行になります。title="${%Prefix}"
上記のJnekinsの管理で表示される文字列を示します。ここではPrefixなのにUI上では接頭辞と表示されています。これは日本語Locale対応をしているためです。英語Localeの環境ではPrefixと表示されます。
日本語Localeに対応するためにはglobal_ja.properties
を用意します。
- 日本語Locale設定ファイル
# Prefix=接頭辞
Prefix=\u63a5\u982d\u8f9e
Unicodeで記述する必要があるので、Unicode Escape Sequence 変換ツールのようなサイトでUnicodeの文字列を用意しましょう。
global.jelly
の<f:entry
の話に戻りますが、この中でも重要なのがfield=prefix
です。
これは以下のgetPrefix
メソッドと対応しています。
そのためperform
メソッドなどからgetPrefix
を実行することでJenkinsの設定の値を取得することが可能になります。
@Extension
public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
private String jobNamePrefix;
public String getPrefix() {
return jobNamePrefix;
}
public boolean configure(StaplerRequest req, JSONObject formData)throws FormException {
jobNamePrefix = formData.getString("prefix");
save();
return super.configure(req, formData);
}
description
やdefault
は想像通りだと思いますので説明は省略します。
1.3. ビルド手順の追加を選択した時に表示される文字列
ビルド手順の追加を選択した時に表示される文字列ですが、ソースコードのDescriptorImpl
クラスのgetDisplayName
メソッドと対応しています。
JekinsのUIでは先ほど作成したcreate-job -> ジョブの設定 -> ビルド手順の追加 -> ジョブの作成になります。
public String getDisplayName() {
return Messages.JobCreateBuilder_DisplayName();
}
Messages
クラスに見覚えがないかと思いますが、これはMessages.properties
, Messages_ja.properties
から自動生成されるクラスになります。
日本語Localeの環境では、Messages_ja.properties
の値が返り、それ以外の環境ではMessages.properties
の値が返ります。
public class Messages {
/**
* Set a job name.
*
*/
public static String JobCreateBuilder_JobName_Empty() {
return holder.format("JobCreateBuilder.JobName.Empty");
}
省略
1.4. ジョブ内部のパラメータ
config.jelly
、config_ja.properties
を作成すると、ジョブ内部のパラメータを作成することが可能です。
JenkinsのUIでは先ほど作成したcreate-job -> ジョブの設定 -> ジョブを作成 -> ジョブ名になります。
<?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="${%JobName}" field="jobName">
<f:textbox />
</f:entry>
</j:jelly>
global.jelly
とソースコードの対応の考え方は先ほどとほとんど同じですが、今回はDescriptorImpl
クラスではなくJobCreateBuilder
のメソッドが対応します。
public class JobCreateBuilder extends Builder {
private final String jobName;
@DataBoundConstructor
public JobCreateBuilder(String jobName) {
this.jobName = jobName;
}
public String getJobName() {
return jobName;
}
1.5 ジョブ内部のパラメータの入力フォームのバリデーション
Jenkinsでは以下のように入力フォームの状態が不正な場合に画面に警告を表示することが可能です。
public FormValidation doCheckJobName(@QueryParameter String value) {
if (value.length() == 0)
return FormValidation.error(Messages.JobCreateBuilder_JobName_Empty());
return FormValidation.ok();
}
1.6. ジョブ内部のパラメータのヘルプ
help-jobname.html
, help-jobname_ja.html
を用意しておくと、以下のようにフォームに対応したヘルプを表示できるようになります。
2. テスト
2.1. JUnit4でユニットテストの基本
以下のように宣言するとテスト用のJenkinsのインスタンスが生成され、各種JenkinsのAPIが利用できるようになります。
これを実行せずに各種APIを実行してもNullPointerException
となるので気をつけてください。
public class JobDeleteBuilderTest {
@Rule
public JenkinsRule j = new JenkinsRule();
@Test
public void testPerform() {
// test code略
}
@Test
public void testDescriptorDoCheckJobName() {
// test code略
}
またJUnit4の仕様になりますが@Test
アノテーションをつけたメソッドがテストケースとして扱われます。
今回作成した2つのテスト関数ですが、testPerform
メソッドは実際にジョブを実行するテストを実施し、testDescriptorDoCheckJobName
ではジョブ名を入力した際のバリデーションのテストを実施しています。
またテストコードの場合には、UI上からジョブの作成やビルドの実行をするわけにはいかないため、APIを実行してジョブの作成などをする必要があります。
2.2 サンプルコードのテストのメインシーケンス
続いてtestPerform()
のメインシーケンスをコメントで簡単に説明します。
``
@Test
public void testPerform() throws Exception {
// create a job. Job name is pre-job01
{
// Jenkinsの設定
String newJobNamePrefix = "pre-";
// ジョブお設定
String newJobName = "job01";
// JobCreateBuilderのジョブを作成
FreeStyleProject createBuilderProject = createJobCreateBuilderProject("create-builder-project01", newJobNamePrefix, newJobName);
// テスト実行
// ジョブの実行
Build build = createBuilderProject.scheduleBuild2(0).get();
// ジョブの実行結果を取得
Result result = getResult(build);
// テスト結果を確認
// ジョブの実行に成功したかどうか
assertThat("Check pre-job01 is success", result, is(Result.SUCCESS));
// 作成したジョブが想定したとおりのものになっているかどうか
FreeStyleProject newJob = (FreeStyleProject) jenkinsRule.getInstance().getItem(newJobNamePrefix + newJobName);
assertThat("Check pre-job01 is created", newJob.getDisplayName(), is(newJobNamePrefix + newJobName));
// 終了処理 作成したジョブを全て削除する。
createBuilderProject.delete();
newJob.delete();
}
}
2.3 ジョブの作成
以下は、JobCreateBuilderのジョブを作成するメソッドです。
private FreeStyleProject createJobCreateBuilderProject(String builderName, String jobPrefix, String jobName) throws IOException {
// 1. FreeStyleProjectを作成
FreeStyleProject project = jenkinsRule.createFreeStyleProject(builderName);
// 2. JobCreateBuilderを作成
JobCreateBuilder job = new JobCreateBuilder(jobName);
// 3. Jenkinsの設定を実施
job.getDescriptor().setPrefix(jobPrefix);
// 1.で作成したFreeStyleProjectに、2のJobCreateBuilderを登録
project.getBuildersList().add(job);
}
2.4. 実行したジョブの結果を取得
実行したジョブの結果を取得する際には注意するポイントが存在します。
このscheduleBuild2
メソッドは、名前のとおりジョブを実行スケジューラに登録するだけなので、このメソッドがreturnされた時点では、まだジョブの実行が終わってない可能性があります。
そのため直接build.getResult()
を実行してしまうと実行が終わっている保証がないため、build.isBuilding()
で実行が終わっていることを定期的にチェックしてから結果を取得します。
今回はジョブの実行が終わった後に実行結果を返すgetResult
メソッドを作成しました。
private Result getResult(Build build) throws InterruptedException {
while(build.isBuilding()) {
Thread.sleep(10);
}
return build.getResult();
}
参考
今回はBuildを拡張するプラグインを紹介していますが、Jenkinsはその他の部分もプラグインとして開発可能になっています。Extension Points - Jenkins Wikiを確認してみてください。
また公式ドキュメントを読み進めても難しいので、最初は目標とするプラグインをJenkins Wiki - PluingsやGithubのjenkisciグループから見つけて参考にしてみましょう。
特にJenkins Wiki - Pluingsはカテゴリごとに分類されているため、自分が作ろうとしているプラグインと似ているものを探しやすいと思います。
あとは自分なりに変更が必要なところはJenkins公式ドキュメントを確認してみてください。
また過去にリリースされたプラグインを参考にしているとdeprecatedになっているAPIを使っているものもあるので、余裕があれば適宜利用しているAPIを公式ドキュメントで確認して最新のAPIに置き換えてみましょう。
まとめ
以上で、開発やテストが完了しました。
続きはJenkinsプラグイン開発ノウハウ3 - リリースになります。