(2015/05/14 追記) このエントリで言及しているSubversionリポジトリを複数指定できない問題ですが、今は解決されています。
個人用メモも兼ねて。
ちょっとこんがらがったプロジェクトではありがちですが、ある特定の環境へのリリースのためにいくつかのジョブで構成されたビルドパイプラインがあり、それを環境ごとに微妙な差分をつけて、複数種類のビルドパイプラインを構成する必要があったりします。
環境の数やアプリの数によっては、これらの差分を含めてJenkins上にジョブを構成しようとすると、下手をすると100個近いジョブに到達することもあります。
(いうまでもないですが、こういうジョブ過多な状態を通常は目指すべきではありません。)
こういう場合に手作業でJenkinsのジョブを構成しようとすると恐ろしいクリックゲームになるので、Jenkins公式が用意しているJob DSL Plugin を使ってプログラミングで大量のジョブを効率よく構成しましょう。
Job DSL Pluginの概要
https://github.com/jenkinsci/job-dsl-plugin/wiki
- 普通にプラグイン設定画面からインストール可能です。入れてない人はぜひ入れましょう。
- GroovyのDSLで、Jenkinsのジョブやビューをサクッと構成可能。
- 意外なほど多種のプラグインに対応している。
- (ちなみにSCM Sync Pluginとこれを一緒に使うと、内部のジョブ定義1つごとにSCMにコミットされます。ジョブ定義をするたびにコミットログがウザいことになりますが、気にしないことにしましょう:-p )
さっそくJob DSLを書いてみる
ローカル開発環境のインストール (Windows可)
JenkinsのJob DSL PluginのGroovyソースのテキストエリアにDSLを貼り付けてもジョブの生成はできるのですが、
- Groovyの文法が正しいのか、
- セマンティックなエラーは起きないのか、
- どういうジョブ定義が出力されるのか、
などなどJenkins上だけで実行ボタンをおしたりしてチマチマ確認するのはとっても面倒なので、ローカルでDSLの実行結果を確認できる環境を作ると非常に捗ります。
詳しい手順は https://github.com/jenkinsci/job-dsl-plugin/wiki/User-Power-Moves に書いてあるのですが、とりあえずここでも紹介しておきます。
まずは、Groovyを扱うため、JDKを入れて使えるようにしておきましょう。
あとはJob DSL Pluginをgit clone
したりして
$> git clone https://github.com/jenkinsci/job-dsl-plugin.git $> cd job-dsl-plugin
ちょっと適当なJob DSLを所定のフォルダにおいてあげましょう。下記のJob DSLをjob-dsl-core/sample.groovy
として保存してください。
def jobNames = ['Alpha', 'Bravo', 'Charlie'] for (jobName in jobNames) { job { name jobName } }
ここまでやったら下記コマンドを実行すれば、
./gradlew run -Pargs=sample.groovy
job-dsl-core/
以下に下記3つのそれぞれジョブの設定が入ったファイルができているはずです。
-rw-r--r-- 1 knjname staff 588 Jan 6 01:29 Alpha.xml -rw-r--r-- 1 knjname staff 588 Jan 6 01:29 Bravo.xml -rw-r--r-- 1 knjname staff 588 Jan 6 01:29 Charlie.xml
(Alpha.xmlの中身) <project> <actions></actions> <description></description> <keepDependencies>false</keepDependencies> <properties></properties> <scm class='hudson.scm.NullSCM'></scm> <canRoam>true</canRoam> <disabled>false</disabled> <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding> <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding> <triggers class='vector'></triggers> <concurrentBuild>false</concurrentBuild> <builders></builders> <publishers></publishers> <buildWrappers></buildWrappers> </project>
基本的にはDSLを編集→gradlew run ...
を実行→ローカルに出力されたXMLの中身を確認→DSLを編集、というループで開発していき、納得できる形になってから最後にJenkins上でDSLを実行という形になると思います。
Jenkins上でJob DSLを動かしてジョブを定義する
特に難しいことはなく、Tutorialにあるように、ジョブ生成用の種ジョブを作成するだけですね。
種ジョブは通常のジョブとほとんどかわらないので、たとえばJob DSLや特定の定義ファイルが更新されたら起動するように種ジョブのトリガをしかけておいて、状況に応じてジョブが勝手に増えるようにすることもできます。
ほか
各プラグインに対応したビュー、ジョブ、トリガー、前処理、後処理などなどの指定方法はWikiを見ましょう。リファレンスが充実しています。意外に対応範囲が広いことに驚くと思います。
Wiki見てピンとこなければGithub上でソースで該当の関数を検索するのがてっとり早いです。
自分の使っているプラグインに該当するDSLがなければ configure
ブロックを駆使して設定ファイルほぼそのまんまで記述しましょう。
ちょっとしたTipsなど
Gradleでの*.groovyファイルの指定方法いくつか
入力となるDSLのgroovyファイルを絶対パスで特定の場所で指定したい人は-Pargs=/root/hoge.groovy
(Windowsなら -Pargs=\C:\Users\root\hoge.groovy
みたいに指定)、複数指定したい人は"-Pargs=hoge.groovy fuga.groovy"
とすれば大丈夫です。
ちなみにXMLの出力場所の指定方法は知りません!
Gradleの起動が遅くてイラつく
gradlew
の起動が遅くてイライラする人はgradlew
のユーザごとプロファイル設定ファイル(~/.gradle/gradle.properties
)に下記の値を指定してGradle用のJVMをデーモン起動させるといいらしいよ!
org.gradle.daemon=true
DSL上で使えるコンテキスト依存の関数の作り方(+Subversionの場合の複数箇所チェックアウト)
たとえば、Subversionを使っている場合、普通に1ジョブ内で複数箇所からチェックアウトすると思うのですが、DSLではこの手のケースは想定されていません!
だから公式ではこうやれってよ!
scm { svn('http://hoge/svn/trunk', 'hoge') { it / locations << 'hudson.scm.SubversionSCM_-ModuleLocation' { remote 'http://fuga/svn/trunk' local 'fuga' } it / locations << 'hudson.scm.SubversionSCM_-ModuleLocation' { remote 'http://baz/svn/trunk' local 'baz' } } }
ロケーションの1個目だけを特別扱いして書いて、あとは無理やり設定XMLまんま書けとかイケてないですね。
せっかくプログラミング言語を使っているので、関数に出してすっきりさせてあげましょう。
def svnRepositories = { repositories -> svn(repositories[0].remote, repositories[0].local) { if(repositories.size == 1) return for(repos in repositories[1..-1]) { it / locations << 'hudson.scm.SubversionSCM_-ModuleLocation' { remote repos.remote local repos.local } } } } // ... scm { svnRepositories([ [remote: 'http://hoge/svn/trunk', local: 'hoge'], [remote: 'http://fuga/svn/trunk', local: 'fuga'], [remote: 'http://baz/svn/trunk', local: 'baz'], ]) }
Groovy的にいいコードなのかどうかわかりませんが、適当に上記のようにしてみました。
しかし、これを実行すると下記のように例外が発生してしまいます。
Exception in thread "main" groovy.lang.MissingMethodException: No signature of method: sample.svn() is applicable for argument types: (java.lang.String, java.lang.String, sample$_run_closure1_closure3) values: [http://hoge/svn/trunk, hoge, sample$_run_closure1_closure3@279fedbd] Possible solutions: run(), run(), any(), find(), wait(), dump() at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:55)
まあ当然といえば当然なのですが、GroovyのDSLと呼ばれているものは、DSLを書く場所に実行時、あらかじめコンテキストオブジェクトがはりついており、コンテキストオブジェクト上のメソッド―上記だとsvn(...)
を呼ぶことによって実装しているわけですね。
ところが外部関数にいくと関数呼び出し元のコンテキストもへったくれもない。見えないというわけです。さあて困った。
なので、下記の苦しい呪文をsvnRepositories([...])
の前に挟みます。
scm { svnRepositories.delegate = delegate // 苦しい呪文 svnRepositories([ [remote: 'http://hoge/svn/trunk', local: 'hoge'], [remote: 'http://fuga/svn/trunk', local: 'fuga'], [remote: 'http://baz/svn/trunk', local: 'baz'], ]) }
この苦しい呪文より、うまくいくようになります。同様にDSL内で関数くくりだしをした場合は上記呪文を挟めばいけます。
(delegate
でコンテキストオブジェクトを参照し、さらに関数の同名のプロパティにあらかじめ設定することで、その関数内のコンテキストオブジェクトを書き換えることができるので、関数呼び出し元のコンテキストオブジェクトを伝播させてやっているわけですね。正直Groovyはまったく詳しくないので、厳密にどうなっているのか知りませんが。)