cloudpack の DJ かっぱ (@inokara) です。

はじめに

Cron の結果をメール通知ぢゃなくて Jenkins に飛ばす試みは以前にもやったことがありましたが、同じことを Go で実装したら面白そうだなって思ったので勉強を兼ねて作ってみました。Jenkins のドキュメントには Java で作られた jar を利用していますが Java をインストールしたくない場合に Go であれば、その辺りのハードルを超えられれないかなと考えています。

作り始めたら一時間位で出来てしまったの(ソースの美しさとか置いといて)で Go の敷居の低さとか先人の方々の努力の結晶には本当に頭が下がります。ありがとうございます。

Cron の結果を Jenkins にポストするには

外部ジョブの監視

Jenkins は以下のようなジョブを作成することが出来ます。

  • フリースタイル・プロジェクトのビルド
  • Mavenプロジェクトのビルド
  • External Job(外部ジョブ)
  • マルチ構成プロジェクトのビルド

その中から External Job を利用します。

XML をポストする

外部ジョブの監視では以下のような XML を Jenkins ジョブの postBuildResult にポストすることで結果を Jenkins に登録します。


  ...コンソール出力を16進数のバイナリエンコードしたもの...
  ... エラーコード(整数)0は成功でそれ以外は失敗
  ... 実行時間(ms) ...
  ... ビルドナンバーの代わりとして表示させたい名前 ...
  ... ビルドの詳細 ...

例えば、curl を利用する場合には以下のように実行することでも結果を登録することが出来ます。

$ curl -X POST -d '4142430A02000' 
                 http://user:pass@myhost/jenkins/job/_jobName_/postBuildResult

実際に実行すると Jenkins 上には以下のように登録されています。
cron の結果を Jenkins にポストするハンドラっぽいのを Go で作ってみた: Jenkinsで動作確認(1)

<log encoding='hexBinary'> ~ </log> の間には上記の通りコンソール出力をバイナリエンコードした内容を記録することで Jenkins にも同じ内容を出力することが可能です(上図では ABC という文字列を登録しています)。また、<result> ~ </result> には cron で実行したスクリプトのステータスコードを 0 やそれ以外の数値を設定します。0 は成功、それ以外は失敗として Jenkins は取り扱います(上図では 0 を登録しているの成功のビルドとなっています)。

メリット、デメリット

cron の結果を「外部ジョブの監視」を利用して Jenkins に登録することで以下のようなメリットがあると考えています。

  • 結果の継続的な可視化(Duration の時間等で cron で動作するスクリプトの実行時間を把握)
  • cron ジョブの一括管理
  • 一覧化されることで結果の見落としを防ぐことが出来る

デメリット(注意点)としては…

  • http リクエストでの登録なので Jenkins への接続が失敗すると cron ジョブの結果を把握することが出来ない
  • Jenkins 見ないと結果判らない
  • ジョブ単位での通知が出来ない(?)

というそもそものお話となります、上記のようにデメリット(注意点)はあるものの、結果の継続的な可視化や複数のホストで稼働している cron ジョブを一覧で管理出来るというのは嬉しいかな…。同様の事が出来る Rundeck というサービスもあるようですので試してみたいなと思っています。

ということで go2jenkins

という名前にしました

ということで cron の実行結果を Jenkins の外部ジョブに登録するスクリプトを go で作ってみました。

使い方

以下のようにソースコードを取得してビルドします。

git clone https://github.com/inokappa/go2jenkins.git
cd go2jenkins
go build go2jenkins.go

ビルドが終わったら以下のように実行してオプションを確認してみます。

$ ./go2jenkins -h
Usage of ./go2jenkins:
  -host="http://localhost:8080": Set Jenkins hostname
  -job="hoge": Set Jenkins job name
  -script="/path/to/foo.sh": Set script

次に以下のように Jenkins で外部ジョブ監視(External Job)を作成します。
cron の結果を Jenkins にポストするハンドラっぽいのを Go で作ってみた: 作成した go2jenkins の使い方(1)

次にリポジトリに同梱している example.sh を利用して以下のように go2jenkins を実行してみます。(※Jenkins はローカルホストで起動している状態です。)

$ ./go2jenkins -script=/path/to/example.sh -host=http://localhost:8080 -job=hogehoge
status:  200 OK

また、crontab への登録は以下のように登録します。

* * * * *       /path/to/go2jenkins -script=/path/to/example.sh -host=http://localhost:8080 -job=hogehoge

尚、example.sh をそのまま実行すると以下のように出力されます。

$ /path/to/example.sh
Sun Jan 18 13:56:13 UTC 2015
Hello Jenkins.
Sun Jan 18 13:56:23 UTC 2015

上記の内容が Jenkins 上で結果の判定を含めて見れるのはちょっと嬉しいかも。

結果を見てみる

Jenkins を見ると以下のように結果が登録されています。
cron の結果を Jenkins にポストするハンドラっぽいのを Go で作ってみた: 作成した go2jenkins の動作確認(1)

ちゃんとコンソールに出力されている内容が Jenkins にも登録されています。また、example.sh が構文エラーになっている場合には以下のようにビルドは失敗している状態として Jenkins には登録されています。
cron の結果を Jenkins にポストするハンドラっぽいのを Go で作ってみた: 作成した go2jenkins の動作確認(2)

さらに cron で定期実行させた結果の一覧は以下のように出力されて継続的な結果の可視化が可能かと思います。
cron の結果を Jenkins にポストするハンドラっぽいのを Go で作ってみた: 作成した go2jenkins の動作確認(3)

go2jenkins を作るにあたって得た知見

hex バイナリエンコード

encoding/hex パッケージを利用します。

package main

import (
    "fmt"
    "encoding/hex"
)

func main() {
    Log := "hogehoge"
    sEnc := hex.EncodeToString([]byte(Log))
    fmt.Println(sEnc)
}

以下のように出力されます。

$ go run example.go
686f6765686f6765

HTTP Post

bytes パッケージと net/http パッケージを利用します。

package main

import (
        "fmt"
        "bytes"
        "net/http"
        "os"
)

func main() {
        body := bytes.NewBufferString("4142430A02000")
        res, err := http.Post("http://localhost:8080/job/test/postBuildResult", "text/xml; charset=utf-8", body)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        fmt.Println("status: ", res.Status)
}

bytes.NewBufferString を利用してリクエストボディを生成しています(なぜ bytes.NewBufferString しているのかは要調査…)。http.post は以下のように実行しています。

res, err := http.Post("http://localhost:8080/job/test/postBuildResult", "text/xml; charset=utf-8", body)

test/xml のようなボディタイプを指定する点に注意する必要があるようです。

上記のサンプルを実行すると以下のように出力されます。

$ go run example.go
status:  200 OK

外部コマンドの実行

Go スクリプト内から外部のコマンドを実行する場合には os/exec を利用します。実行した外部コマンドの標準出力及び標準エラーを得るには以下のように書くようです。こちら「golang で外部コマンドを実行して標準出力を取得する」を参考にさせて頂きました。ありがとうございます。

package main

import (
        "fmt"
        "bytes"
        "os/exec"
)

func main() {
        //
        var stdout bytes.Buffer
        var stderr bytes.Buffer
        var Log string
        //
        cmd := exec.Command("date")
        cmd.Stdout = &stdout
        cmd.Stderr = &stderr
        err := cmd.Run()
        if err != nil {
                Log = string(stderr.String())
        }else{
                Log = string(stdout.String())
        }
        fmt.Println(Log)
}

実行すると以下のように date コマンドの実行結果が表示されます。

$ go run example2.go
Sun Jan 18 15:00:10 UTC 2015

ということで…

今回作ったスクリプトはまだまだ修正しなければいけない点がいくつかありますが、プログラムとはほぼ無縁な自分でも色々な「型」に気をつける必要はありつつもシェルスクリプト感覚で思った物が作れる Go って面白いなと思います。また、Jenkins を利用した cron ジョブの監視スクリプトはツッコミどころはいくつかありますが、Go で作ったことによりビルドしたバイナリ一個を設置するだけで結果の見落とし防止やジョブの管理として利用することは出来る点は嬉しいと思っています。

ということで…おやすみなさい。

元記事はこちらです。
cron の結果を Jenkins にポストするハンドラっぽいのを Go で作ってみた