Jenkins2をコード化しよう - その2:Jenkinsの起動時にプログラム(Groovy Hook Script)を動かす

Jenkins2をコード化しよう - その2:Jenkinsの起動時にプログラム(Groovy Hook Script)を動かす
目次

前回の記事 Jenkins2をコード化しよう - その1:Ansibleでプロビジョニングする では Jenkins2をAnsibleでコード化するためのTipsを紹介しました。

今回は前回触れなかった init.groovy.d のJenkins起動スクリプトについて説明します。

init.groovy.d ディレクトリとは

Jenkinsの公式ページ に Groovy Hook Script に関する記載があります。 Groovy Hook Scriptを使うことで、Jenkinsアプリケーションの指定のタイミングで任意のプログラムを実行するのに役立ちます。

Groovyスクリプト配置可能場所と探索の優先順位を以下に引用します。 なお、HOOK は 2019/05現在では initboot-failure のタイミングがあり、それぞれ、Jenkinsが起動するタイミングと、起動に失敗したタイミングをフックできます。

These scripts are written in Groovy, and get executed inside the same JVM as Jenkins, allowing full access to the domain model of Jenkins. For given hook HOOK, the following locations are searched:

WEB-INF/HOOK.groovy in jenkins.war

WEB-INF/HOOK.groovy.d/*.groovy in the lexical order in jenkins.war

$JENKINS_HOME/HOOK.groovy

$JENKINS_HOME/HOOK.groovy.d/*.groovy in the lexical order

jenkins.warの中に直接Groovyファイルを配置する手間と、実行ファイルが複数あっても良いことを鑑みると、${JENKINS_HOME}/init.groovy.d/ 配下にファイルを配置するのが割と自然な選択でしょう。

デバッグの仕方

まず覚えて置くと良いのは、作成したGroovy Scriptのデバッグの仕方です。 Groovy Scriptを使ってJenkinsの設定を変更するコードを書きたいときには、Jenkinsに関連するライブラリが必要なのですが、ローカル環境を構築するのが手間です。 そのため、Jenkinsの [Manage Jenkins] > [Script Console] からGroovy Scriptの動作確認をしながら実装することをオススメします。

script_console

なお、Script Console上では jenkins.* jenkins.model.* hudson.* hudson.model.* がデフォルトでインポートされています。 実際にgroovyファイルを作成するときはこれらもgroovyファイル内でインポートする必要があります。

外部のライブラリを使いたい場合

外部のJavaライブラリを使いたいときがよくあります。 例えば、JenkinsをAWS上に構築しているのであれば、AWS SDK for JAVA などがそれに該当します。

このようなユースケースではJenkinsのwarファイルに細工をするのではなく、Grape.grab を使うと良いでしょう。 以下のように書けば、Groovy Script内で使う外部モジュールを、スクリプトの実行タイミングで取得してくれます。

1// https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk
2groovy.grape.Grape.grab(group:'com.amazonaws', module:'aws-java-sdk', version:'1.11.534', transitive: false)
3
4import jenkins.model.*
5import hudson.security.*
6// 以下にgroovyの処理を書く(割愛)

サンプルコード:KMSの情報を復号化してJenkinsのCredentialへ登録

試しにGroovyのサンプルコードを書いてみましょう。KMSから取得した鍵情報を復号化して、JenkinsにCredentialを登録するサンプルです。

 1// https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk
 2groovy.grape.Grape.grab(group:'com.amazonaws', module:'aws-java-sdk', version:'1.11.534', transitive: false)
 3
 4import jenkins.model.*
 5import hudson.security.*
 6import com.cloudbees.plugins.credentials.SystemCredentialsProvider;
 7import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey;
 8import com.cloudbees.plugins.credentials.CredentialsScope;
 9import org.jenkinsci.plugins.plaincredentials.*
10import org.jenkinsci.plugins.plaincredentials.impl.*
11import hudson.util.Secret
12
13import org.apache.commons.fileupload.*
14import java.nio.ByteBuffer;
15import java.util.Base64;
16import com.amazonaws.services.kms.AWSKMS;
17import com.amazonaws.services.kms.AWSKMSClient;
18import com.amazonaws.services.kms.AWSKMSClientBuilder;
19import com.amazonaws.services.kms.model.DecryptRequest;
20
21// Jenkinsのインスタンスを取得
22def instance = Jenkins.getInstance()
23
24println "JenkinsのCredentialを登録開始します"
25def system_credentials_provider = SystemCredentialsProvider.getInstance()
26
27// KMSのキー情報
28def ssh_key_description = 'description of key'
29def ssh_key_scope = CredentialsScope.GLOBAL
30def ssh_key_id = 'key_id'
31def ssh_key_username = 'username'
32def ssh_key_passphrase = 'passphrase'
33def ssh_key_private_key_source = new BasicSSHUserPrivateKey.DirectEntryPrivateKeySource(decrypt('CyperText of KMS'))
34
35// 認証情報を追加
36system_credentials_provider.addCredentials(
37  com.cloudbees.plugins.credentials.domains.Domain.global(),
38  new BasicSSHUserPrivateKey(
39        ssh_key_scope,
40        ssh_key_id,
41        ssh_key_username,
42        ssh_key_private_key_source,
43        ssh_key_passphrase,
44        ssh_key_description)
45)
46
47// KMSから復号化します
48String decrypt(String src) {
49  println "KMSから鍵情報を復号化します"
50  byte[] cipherText = Base64.getDecoder().decode(src);
51  ByteBuffer cipherBuffer = ByteBuffer.allocate(cipherText.length);
52  cipherBuffer.put(cipherText);
53  cipherBuffer.flip();
54
55  AWSKMS kmsClient = AWSKMSClientBuilder.defaultClient();
56  DecryptRequest req = new DecryptRequest().withCiphertextBlob(cipherBuffer);
57  return getString(kmsClient.decrypt(req).getPlaintext());
58}
59
60// ByteBufferからStringへ変換します
61String getString(ByteBuffer b) {
62  byte[] byteArray = new byte[b.remaining()];
63  b.get(byteArray);
64  return new String(byteArray);
65}
66
67// Jenkinsインスタンスに保存
68instance.save()
69println "Jenkinsへの登録が完了しました"

ポイントになるのは、 スクリプト内の処理が終了するタイミングまでに、Jenkins.getInstance() で取得したJenkinsのインスタンスオブジェクトを保存することです。

参考にさせていただいたサイト