Jenkins2.0のPipeline活用法(4月現在)

jenkins_image

Pipelineをつかいたい!!

新しいものっていいですよね。僕も大好きです!

問題は現状のpipelineはまだまだ開発途上で、すべてを解決してくれる夢のツールではないのです。

なので執筆時点2017年3月での工夫のしどころや無難な使い方をいくつか羅列していきます。

あくまでも現状ですし、とて活発に開発が進んでいるのが現状ですのですぐに陳腐化すると思いますが、早く使いたくてたまらない新しいもの大好き人間の皆さんに向けて。

 

スクリプトのコツ

この章では、pipelineスクリプトを記述するときに、気に留めておくべき幾つかのことを紹介します。

1.バグってる前提でいる

これ現状では大前提です。

基本どこかバグってます。

あるバージョンだとPATHがうまく読めてなかったり。あるバージョンだとジョブを止めると程よい確率でデッドロック起こしたりします。

基本的に最新版にしておきましょう。それが安定している保証はありませんが一番マトモだと推定できます。

 

2.Java由来のクラスをあまり使わない

groovyなのでJava由来のクラスなどを使いたくなるかも知れませんが、やめたほうがいいです。

CPSというセキュリティ系の機構が入っていてたいてい面倒なことになります。

NonCPSという回避策もありますがログがうまく出なかったりするので極力使わないほうがいいです。

幸いすでにreadFileやreadPropertiesという設定を読み込む部分が用意されていたりハッシュマップは使用できるのでJenkins側が用意しているAPIを使用しましょう。
(ちなみにreadYamlの戻り値をforeachなどで回そうとするとNonCPSが必要になるパターンがあります)

また、ファイルの読み書きを伴う操作は絶対にjava.io系を使わないでください。実行groovyのパスが全然workspaceと違うのでまともに使えないです。readFileやshをつかいましょう。

あなたが今書いている言語は何かを間違えない

pipelineスクリプトはgroovyですがshステップの中身はshellですし、batステップの中はバッチコマンドになります。ほかにもmvnやらyamlやらあります。
特に文字列の埋め込みで問題になることが多いですのでちょっと気にするといいと思います。あとエスケープもけっこうはまります。
windows batだと\でなければいけないところ/にしてしまっていてうまく動かないなど

for inを使わない

for inはCPSと相性が悪いです。理由としてはイテレータがNonSerializableだから…
なので基本死にますついでにNonCPSつけててもお構いなしに死にます…

おとなしくfor使っときましょう。eachとかいう贅沢品もございません。
幸い基本的にコレクションにはtoArrayメソッドが定義されているので何とかなります。とってもいもくさいけど

NoCPSの変数は最後nullで消す

これをしないと例外になる可能性があります

 

具体的なサンプル

上記のことを考慮に入れて今幾つかのスクリプトの組み方のサンプルを示します。

フリースタイルジョブ同士の流れを決めるハブのような役割にする

例えば失敗したら何度かリトライをして最後の一回に失敗したときだけエラー通知を送る、設定によって条件分岐するなどはとてもフリースタイルジョブが苦手とする部分ですが、これはpipelineジョブの得意とするところです。
ですのでpipelineジョブには難しいことはさせずにフリースタイルのマネジメントに徹してもらう方法が一つあります。



// propagate: falseを入れないとしこのジョブが失敗した段階でジョブが止まる
// ビルドパラメータはそのまま使える けど他の予約されているものと衝突すると使えないので注意
def result;
retry(3){
    result = build job: 'AnyJob', parameters: [string(name: 'Param', value: BuildParam)], propagate: false
}

//三回やってそれでも失敗したらエラー処理をする
//resultは文字列で来るので注意
if( result == 'FAILURE' )
{
//エラー処理とか
    build job:'ErrorNotifier', parameters:[string(name: "message", value: "失敗しました直してください")]
    throw new Exception();
}

print "ビルドに成功しました"

シェルスクリプトなどを呼び出すスクリプトとして使う

サーバーデプロイなんかはこの方法が良いと思います

究極この一行くらいになるかな
フリースタイルより設定簡単なのが利点です。


sh(readFile("deploy.sh"))

もしくはshステップを使ってスクリプト内にスクリプトを書きます。
retryとかももちろんできます
例えばこんな感じです


def ServerDeploy()
{
        ws("${TargetDirectory}")
        {
            sh  '''source ~/.bash_profile
                cd ${TargetDirectory}
                sh deploy.sh
                '''
        }
}

retry(3){
    ServerDeploy()
}

設定ファイルを読みだして処理をしていく

極力スクリプトの修正は避けたいのです。
そこでできるだけ設定は外だししてしまいましょう。
Jenkinsfileと同じリポジトリで管理すれば設定を間違えてもすぐに元に戻せたりもします。

設定ファイルを読み込むのは簡単ですreadPropertiesがあります。
さらにここで読み込んだ設定を下流ビルドに流したい場合は、そのままではうまくいかないので以下のメソッドを定義して使ってください。(ちょっと古い形式使っちゃってます。)


@NonCPS
def convertBuildParameters(config){
    def list = []
    for (c in config)
    {
        key = c.key
        value = c.value
        paramClass = ""
        if(value.class == String.class){
            if(value.contains("\n") || value.contains("\r\n"))
                paramClass = "TextParameterValue"
            else
                paramClass = "StringParameterValue"  
        }else if(value.class == Boolean.class){
            paramClass = "BooleanParameterValue"
        }
        def param = [$class:paramClass, name: key, value: value]
        list << param
    }
    
    return list
}

最後に

ここまでいろいろ書いてきましたが現状pipelineのコードを書くのは相当癖の強い作業になると思います。(主にCPSのせい)
ですのでいろいろ書いてみてコツをつかんでいくというのがいいアプローチになります。

Jenkinsおじさんのご機嫌をとるような作業になりますがそのうち大体こんなもんかとなれるのでいろいろ試してみることをお勧めします。