Jenkins覚書

## 概要

Jenkins-Pipelineについての覚書レベルのものを羅列。
嵩張りそうなら個別のページに切り出す。

## 個別ページに切り出した項目

https://qiita.com/Shaula/items/cc71dc9ac734c772e16a

https://qiita.com/Shaula/items/635ccb4a984b33534f7a

## pipeline中に使える変数

`${jenkinsmachineのURL}/pipeline-syntax/globals`で確認できる。

## Jenkinsfileに`parameters`を足した時などに一度リロードさせたい

1度実行しないと`parameters`は反映されない。
そのため、以下のような下準備を行っておくとよいと思われる。

```groovy
pipeline {
  parameters {
    booleanParam(name: 'RELOAD_PIPELINE', defaultValue: false, description: 'Job定義ファイルを再読み込みして即終了(ABORT)する')
  }

  stages {
    stage('Reload') {
      when { expression { params.RELOAD_PIPELINE } }
      steps { script {
        echo 'Reload'
        currentBuild.result = 'ABORTED'
        error('Reload jenkins files.')
      } }
    }
  }
}
```

`currentBuild.result = 'ABORTED'`しておくことで`post { failure { ... } }`の処理が呼ばれないので便利！

## マスターマシンとビルド(スレーブ)マシンが違う場合における処理のShared Libraries(モジュール)化

基本は↓を参照。
- https://www.jenkins.io/doc/book/pipeline/shared-libraries/
- https://www.jenkins.io/doc/pipeline/steps/workflow-cps-global-lib/

ただ、マスターマシンとビルド(スレーブ)マシンが違う場合には上記の処理ではうまく読み込めなかった。(詳しい方アドバイスをいただきたいです)

色々試した結果、以下のようにするとスレーブマシンでもモジュールを読み込めたので記載する。

1. 任意のレポジトリの`トップレベルディレクトリ`に`vars`という名前のディレクトリを作成
1. `vars`内に`${some_name}.groovy`というgroovyスクリプトを置く
    - 中身は`def call() {`から始まるcall関数(パラメータがあってもよい)があれば関数やクラスの宣言があっても問題ない模様
        - e.g.
          ```TriggerJob.groovy
          def call(jobName) {
              build(
                job: "${jobName}",
                wait: false,
                parameters: []
              )
          }
          ```
    - groovyではあるが、Jenkins-Scriptなので`withEnv`や`sh`、`withCredentials`など`steps`に記述できるものは何でも記述可能である模様(**未検証**)
1. 呼び出し元のJob内で`script { library(identifier: 'lib@master', retriever: legacySCM(scm)) }`を呼び出す。
    - e.g.`stage('Import') { steps { script { library(identifier: 'lib@master', retriever: legacySCM(scm)) } } }`
        - identifierは何でもいい模様
        - retrieverは`legacySCM(scm)`以外だとうまくいかなかった。(**要検証**)
1. 呼び出したいstepsで`some_name()`と書くだけで呼び出しが可能
    - e.g.`stage("trigger_job") { steps { TriggerJob("other_job_name") }`

## groovy変数をsh内で参照する方法

groovy変数をsh内で使用するには以下の方法がある。

1. `sh "...${variable}..."` のように`"`の中に書く
1. `sh '...' + variable + '...'`や`sh '''...''' + variable + '''...'''`のように一度文字列を終了させて+で連結させる。
1. 環境変数化
    - 参考 : https://e.printstacktrace.blog/jenkins-pipeline-environment-variables-the-definitive-guide/
    1. withEnvブロックを使う
        - e.g. `withEnv(["VARIABLE=" + variable]) { sh '..."${variable}"...' }`
        - 基本はこれを使用すれば良さそう
    1. environmentブロックを使う
        - e.g. `stage(...) { environment { VARIABLE = "${variable}" } steps {...} }`
        - groovyとしてわざわざ使う機会はないはず？
    1. `env.NAME`に代入する
        - e.g. `env.VARIABLE = variable`
        - 既に定義済みの環境変数を上書きすることはできない点に注意

## 変更ログを取得する

変更ログの取得方法は2通りあり、APIを使用する方法とgroovy変数を参照する方法である。

### API

以下のURLにアクセスすればよい。

`${BUILD_URL}api/xml?xpath=//changeSet//comment/text()&wrapper=changes`

解説すると以下のような感じ。

- `api/xml`はXML形式で結果を返すことを指す。
    - 他に`api/json`やpython/ruby/javaなどもあるらしい。(**未検証**)
    - 参考 : https://www.jenkins.io/doc/book/using/remote-access-api/
    - 単にjobの実行URLに`api/xml`を付けただけでも様々な内容が返ってくる。
- `xpath=//changeSet//comment/text()`
    - `xpath`はXPathというコマンド？(**未調査**)と同じ挙動をさせるらしい。
    - `//`はいずれかの階層とのこと。changeSetは`workflowRun/changeSet`でもアクセスできるが、`//`と書くことにより、任意の階層にマッチできる。
        - よくあるパス指定での`/**/`みたいなものだと思っている。(**未検証**)
    - `text()`は`//comment`でも取ってこれるが、text形式のものだけ抽出する(**未調査**)ことができるので楽っぽい。
    - `xpath=//comment/text()`だけでも要件は満たせたが、`changeSet`も指定した方が厳密な気がしたので採用している。
- `wrapper=changes`は最上位のタグを`changes`にする？(**未調査**)
    - とりあえず必要なので付けている。
    - `sed`あたりにpipeで渡してあげれば良さそう。

注意点
- ユーザー認証などがある場合はAPI-Tokenの用意などが必要。

### groovy変数(currentBuild.changeSets)

以下のコードで動作した。

```groovy
def getChangeMessages() {
    def messages = ""
    for (entries in currentBuild.changeSets)
    {
        for (entry in entries.items)
        {
            messages += "${entry.msg}\n"
        }
    }
    return messages
}
```

## groovy変数名について

**未検証**

以下のような機能を作成した際、

```groovy
def getChanges() {
    def changes = ""
    for (entries in currentBuild.changeSets)
    {
        for (entry in entries)
        {
            changes += "${entry.msg}\n"
        }
    }
    return chages
}
```

以下のようなエラーが出て困ったことがある。

```
groovy.lang.MissingPropertyException: No such property: chages for class: groovy.lang.Binding
Possible solutions: class
	at groovy.lang.Binding.getVariable(Binding.java:63)
	at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onGetProperty(SandboxInterceptor.java:270)
	at org.kohsuke.groovy.sandbox.impl.Checker$6.call(Checker.java:291)
	at org.kohsuke.groovy.sandbox.impl.Checker.checkedGetProperty(Checker.java:295)
	at com.cloudbees.groovy.cps.sandbox.SandboxInvoker.getProperty(SandboxInvoker.java:29)
	at com.cloudbees.groovy.cps.impl.PropertyAccessBlock.rawGet(PropertyAccessBlock.java:20)
	at MyModule.getChanges(MyModule.groovy:11)
	at MyModule.call(MyModule.groovy:16)
```

散々試した結果、`changes`という変数名を`messages`に変更したら大丈夫だった。
`MissingPropertyException`を見かけたら名前を変えてみるといいのかも？

## API呼び出し結果を検証する

Jenkinsというよりはshの挙動なのだが、CIに関わってくるので記載。

`curl -I ${url}`でhttp_codeを取得できるのだが、これは`GET`にしか使用できない。
そのため、`POST`で使用するには以下のようにする必要がある。(`GET`にも応用はできるはず)

`curl -w '%{http_code}' -o /dev/null ${post_parameters} ${url}`

だが、これだとレスポンスの内容が取得できないので以下のようにする必要がある。
`-w`で指定する内容にcontentの内容が含まれていない点に注意。

```bash
set -e
response=$(curl -w '\n%{http_code}' ${post_parameters} ${url})
http_code=$(echo "${response}" | tail -n1) # "でエスケープしないと改行が変になるため正常に取得できない
test ${http_code} = "200" # 200以外ならエラー終了
```

レスポンスの内容も出したいなら↓のようにする。

```bash
echo "${response}" | head -n -1
```

### ダウンロード進捗表示

`curl ... 2>/dev/null`しなくても`curl -s ...`で消せる。