この記事はマネーフォワード アドベントカレンダー20日目の記事です。
皆さん、Jenkins使ってますか!
今年発足したCDF(Continuous Delivery Foundation)でホストされているJenkinsは、CI/CDを語る上では切っても切り離せない存在です。今回、とある事情によりJenkinsのジョブを(コンソール上から手作業でビルドするのではなく)リモートでビルドしたくなりました。
みなさんはそんな時にどうしますか!?
ご存知のとおり、JenkinsはAPIを提供しています。例えば、ジョブの一覧を取得する際には、以下のエントリポイントにリクエストを投げることでjsonレスポンスが返ってきます。(以下の例は、APIアクセスに認証が必要な場合です)
$ curl -s -u <USER>:<TOKEN> https://<JENKINS_HOST>/api/json | jq .
{
"_class": "hudson.model.Hudson",
"assignedLabels": [
{
"name": "master"
}
],
・・・
APIは参照系だけではありません。以下のエントリポイントにPOSTリクエストを投げることで、ジョブをビルドすることができます。
curl -X POST -s -u <USER>:<TOKEN> https://<JENKINS_HOST>/job/<JOB_NAME>/build
このようにリクエストを投げればリモートでジョブをビルドできるのですが、毎回curlを実行するのも面倒ですよね。 そこで、gojenkinsというgolang製のJenkins APIクライアントライブラリを使って、Jenkinsのジョブをリモートでビルドするスクリプトを書いてみます。
前提
環境準備
以下の環境が前提となります
- http/httpsでアクセスできるJenkinsが存在する
- Goのプログラムを実行できる
- JenkinsのAPI_TOKENを発行済で、Goのプログラムから利用可能(環境変数に設定しておくとよい)
gojenkinsをインストール
gojenkinsをインストールしておきましょう。
go get github.com/bndr/gojenkins
Jenkinsにテスト用ジョブを追加する。
まずはじめに、Jenkinsにテスト用のジョブを追加します。処理内容は何でもいいのですが、今回はパラメータ(sleep_count)で渡した数値の分だけsleepするという、シンプルなジョブを用意しました。
リモートビルドスクリプトを書く
実際にスクリプトを書いていきます。まずはinitializeです。CreateJenkins()
で、新しいJenkin instanceを生成します。パラメータには、URL、アクセスユーザ、アクセストークンを渡します。instanceを生成した後には、Init()
メソッドで初期化する必要があります。
jenkins := gojenkins.CreateJenkins(nil, jenkinsRootURL, jenkinsAccessUser, jenkinsAccessToken)
_, err := jenkins.Init()
if err != nil {
log.Fatal(err)
}
ビルドパラメータをマップで定義します。BuildJob()
で指定したジョブをビルドします。1つ目の引数にはジョブ名を、2つ目の引数にはパラメータを指定しています。マップのバリューはstring型で渡さないといけないので、intではなく文字列で定義しています。今回は30秒間sleepさせたいのでsleep_count:30
をビルドパラメータに指定します。
params := map[string]string{"sleep_count": "30"}
BuildJob()
でジョブをビルドします。1つ目の引数にはジョブの名前を、2つ目の引数には先程定義したパラメータのマップを指定します。
log.Printf("%s remote build", jobName)
_, err = jenkins.BuildJob(jobName, params)
if err != nil {
log.Fatal(err)
}
BuildJob()
を実行してAPIを叩いた直後は、最新のビルドのステータスがpending
になっています。ステータスがrunnning
になるまで待ちたいので、10秒ほどsleepします。
time.Sleep(time.Second * 10)
一番新しいビルドのステータスを定期的にチェックします。runnning状態でなくなったら、success/failedをチェックし、それに応じてログを出力・スクリプトを終了します。またジョブがロングランしている場合にも、タイムアウトでスクリプトが終了するようにしています。
build, err := jenkins.GetJob(jobName)
if err != nil {
log.Fatal(err, ": maybe Job does not exist")
}
LastBuild, err := build.GetLastBuild()
if err != nil {
log.Fatal(err)
}
deadline := time.Now().Add(10 * time.Minute)
for time.Now().Before(deadline) {
log.Printf("LastBuild.IsRunning() is %t", LastBuild.IsRunning())
if !LastBuild.IsRunning() {
if LastBuild.IsGood() {
log.Print("building job is success")
os.Exit(0) // 正常終了
}
log.Print("building job is failed")
os.Exit(1) // 異常終了
}
time.Sleep(10 * time.Second)
}
log.Print("building job check is timeout")
os.Exit(1)
リモートでビルドしてみる
上記のプログラムを実行すると、以下のようなログが出力されるとともに、Jenkins上でジョブがビルドされていることがわかります。
[I] gojenkins » go run gojenkins_sample.go
2019/12/18 20:38:47 sleep_job remote build
2019/12/18 20:38:57 LastBuild.IsRunning() is true
2019/12/18 20:39:07 LastBuild.IsRunning() is true
2019/12/18 20:39:17 LastBuild.IsRunning() is true
2019/12/18 20:39:27 LastBuild.IsRunning() is false
2019/12/18 20:39:27 building job is success
おわりに
このように、gojenkinsを使うことでGoのプログラムからリモートでJenkins上のジョブをビルドすることができます。
これを活用すると何ができるでしょうか。例えば、複数Jenkins間のジョブフローの同期を簡易的に行うことができます。また、Slack Botと組み合わせればCI/CDのChatOpsも実現できそうです。
この世界にはJenkinsの運用に苦しめられている人もいるかと思いますが、このようなちょっとした工夫を活用して、日々の運用を楽にしていきましょう。