はじめに
GitLabでJenkinsのジョブを実行し結果を読み取りたかったのでGitLabのCI/CD機能とJenkinsを統合を試してみました。
GitLabはGitHub Actionsと似たようなGitLab CI/CD1という機能を備えています。一般的なユースケースではないため、かなり苦労したのでハマったポイントなどをまとめた物を投稿します。
GitHub ActionsとJenkinsを統合した時の記事はこちらをご覧ください
GitHub ActionsからJenkinsのCIジョブを利用する
目的
- GitLabのCIツールでJenkinsのジョブを作動させ、結果を読み込む。
- MRの内容によって違うジョブを作動させたい。
利益
限界
- 結局のところ、JenkinsのAPIをポーリングするだけ。
GitLabのCIツールの基本的な使い方
GitLabはプロジェクトディレクトリにある.gitlab-ci.ymlというYMALファイルを認識します2。基本的にはステージとジョブを宣言するだけでpipelineが成立します。ジョブの記述は基本的にシェル環境で実行するコマンドを順に並べる感じです。
stages:
- build
- test
build-code-job:
stage: build
script:
- echo "Check the ruby version, then build some Ruby project files:"
- ruby -v
- rake
test-code-job:
stage: test
script:
- echo "If the files are built successfully, test some files with one command:"
- rake test
JenkinsでAPIを使えるようにする
Jenkinsのユーザー設定(http://{jenkins_server_url}/user/{user_id}/configure
)でAPIトークン
を発行します。APIトークン
を使ってAPIを呼ぶ時は下記の二通りの方法があります。
curl -X POST --user "{user_id}:{api_token}" "http://{jenkins_server_url}/job/{job_name}/build"
curl -X POST "http://{user_id}:{api_token}@{jenkins_server_url}/job/{job_name}/build"
実際のPipelineスクリプト
全体像
variables:
JENKINS_JOB_URL: "http://jenkins_url/job"
GITLAB_URL: "http://gitlab_url"
stages:
- ready
- build
- test
Check MR:
stage: ready
only:
- web
- schedule
- merge_request
script:
- |-
echo "Initialing..."
if [ "$CI_PIPELINE_SOURCE" == "merge_request_event" ]
then
echo "REQUEST_SOURCE_BRANCH_NAME=${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}" >> build.env
else
echo "REQUEST_SOURCE_BRANCH_NAME=${CI_COMMIT_REF_NAME}" >> build.env
fi
artifacts:
reports:
dotenv: build.env
Building:
stage: build
needs: ["Check MR"]
dependencies:
- Check MR
only:
- web
- schedule
- merge_request
variables:
JENKINS_JOB_NAME: "build_job"
script:
- response=$(curl -si -w "\n%{size_header},%{size_download}" -X POST --user $JENKINS_USER:$JENKINS_TOKEN "${JENKINS_JOB_URL}/${JENKINS_JOB_NAME}/buildWithParameters?BRANCH=${REQUEST_SOURCE_BRANCH_NAME}")
- header_size=$(sed -n '$ s/^\([0-9]*\),.*$/\1/ p' <<< "${response}")
- headers="${response:0:${header_size}}"
- if ! echo ${headers} | grep -q "201 Created"; then exit 1; fi
- target_queue_id=$(sed -n 's/^.*item\/\([0-9]*\).*$/\1/ p' <<< "${headers}")
- while ! curl --silent --globoff --user "$JENKINS_USER:$JENKINS_TOKEN" "${JENKINS_JOB_URL}/${JENKINS_JOB_NAME}/api/xml?tree=builds[id,number,result,queueId]&xpath=//build[queueId=$target_queue_id]" | grep \<result\> > /dev/null; do echo "Waiting..."; sleep 15s; done;
- result=$(curl --silent --globoff --user "$JENKINS_USER:$JENKINS_TOKEN" "${JENKINS_JOB_URL}/${JENKINS_JOB_NAME}/api/xml?tree=builds[id,number,result,queueId]&xpath=//build[queueId=$target_queue_id]")
- build_id=$(sed -n 's/^.*<id>\([0-9]*\)<\/id>.*$/\1/ p' <<< "${result}")
- status=$(sed -n 's/^.*<result>\([A-Z]*\)<\/result>.*$/\1/ p' <<< "${result}")
- if [ "$status" == "SUCCESS" ]; then echo "BUILD_NAME=$JENKINS_JOB_NAME" >> build.env; echo "BUILD_NUMBER=$build_id" >> build.env; exit 0; else exit 1; fi
artifacts:
reports:
dotenv: build.env
Testing:
stage: test
needs: ["Check MR", "Building"]
dependencies:
- Check MR
- Building
only:
- web
- schedule
- merge_request
variables:
JENKINS_JOB_NAME: "test_job"
script:
- response=$(curl -si -w "\n%{size_header},%{size_download}" -X POST --user $JENKINS_USER:$JENKINS_TOKEN "${JENKINS_JOB_URL}/${JENKINS_JOB_NAME}/buildWithParameters?BUILD_NAME=${BUILD_NAME}&BUILD_NUMBER=${BUILD_NUMBER}")
- header_size=$(sed -n '$ s/^\([0-9]*\),.*$/\1/ p' <<< "${response}")
- headers="${response:0:${header_size}}"
- if ! echo ${headers} | grep -q "201 Created"; then exit 1; fi
- target_queue_id=$(sed -n 's/^.*item\/\([0-9]*\).*$/\1/ p' <<< "${headers}")
- while ! curl --silent --globoff --user "$JENKINS_USER:$JENKINS_TOKEN" "${JENKINS_JOB_URL}/${JENKINS_JOB_NAME}/api/xml?tree=builds[id,number,result,queueId]&xpath=//build[queueId=$target_queue_id]" | grep \<result\> > /dev/null; do echo "Waiting..."; sleep 15s; done;
- result=$(curl --silent --globoff --user "$JENKINS_USER:$JENKINS_TOKEN" "${JENKINS_JOB_URL}/${JENKINS_JOB_NAME}/api/xml?tree=builds[id,number,result,queueId]&xpath=//build[queueId=$target_queue_id]")
- status=$(sed -n 's/^.*<result>\([A-Z]*\)<\/result>.*$/\1/ p' <<< "${result}")
- if [ "$status" == "SUCCESS" ]; then exit 0; else exit 1; fi
使用している変数
Predefined variables3
GitLabが提供する情報
- CI_PIPELINE_SOURCE:トリガーの種類
- CI_MERGE_REQUEST_PROJECT_ID:MRのプロジェクトID
- CI_MERGE_REQUEST_IID:MRのID
- CI_MERGE_REQUEST_SOURCE_BRANCH_NAME:MRのソースブレンチの名前
- CI_COMMIT_REF_NAME:パイプラインが作動されたブレンチの名前
Variables in the .gitlab-ci.yml file
- JENKINS_JOB_URL: JenkinsのURL+JOB
- GITLAB_URL:GitLabのURL
Project CI/CD variables
変数を保護するためにProject variablesを使います
- JENKINS_USER:Jenkins APIのためのJenkins User
- JENKINS_TOKEN:Jenkins APIのためのTOKEN
Variables in artifact "build.env" file
ステージ間の値の受け渡しのための変数
- REQUEST_SOURCE_BRANCH_NAME:MRのブレンチの名前
- BUILD_NAME:JenkinsのBuildジョブの名前
- BUILD_NUMBER:JenkinsのBuildタスクのナンバー
GitLabのCIツールでJenkinsのジョブを作動させ、結果を読み込む
GitLab CI/CDの制御
CIツールが作動する条件を設定したい
Job_Name宣言の時only
を通してCIが作動する条件を設定できます4。またトリガーの種類によりPredefined variablesで提供される情報に違いが出ますのでこれをスクリプトの方で処理する必要があります。
Check MR:
only:
- web
- schedule
- merge_request
script:
- |-
echo "Initialing..."
if [ "$CI_PIPELINE_SOURCE" == "merge_request_event" ]
then
echo "REQUEST_SOURCE_BRANCH_NAME=${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}" >> build.env
else
echo "REQUEST_SOURCE_BRANCH_NAME=${CI_COMMIT_REF_NAME}" >> build.env
fi
依存関係を設定したい
Job_Name宣言の時needs
を通してCIが作動する条件を設定できます5。この設定は実際のジョブの実行順序に影響します。また始めのスクリンショットのように依存関係のグラフを生成する時も利用されます。
Testing:
needs: ["Check MR", "Building"]
ジョブ同士で環境変数を渡したい
dotenvルールに沿って作成されたファイルをartifacts
で指定することで次のジョブの環境変数を設定することができます6。当然、このオプションを使う時はneeds:artifacts
7やdependencies
8による依存関係の設定が必須になります。
Check MR:
artifacts:
reports:
dotenv: build.env
Testing:
dependencies:
- Check MR
Jenkinsとの繋げ方
JenkinsのジョブをAPIから実行
基本的にはトークンと一緒にPOST
でREST APIを叩くだけです。パラメーターがある場合はbuildWithParameters
をない場合はbuild
を使います。-w "\n%{size_header},%{size_download}"
はヘッダーを読み取る時に使います。最後にヘッダー利用して要請が正常的に実行されたかを確認します。
response=$(curl -si -w "\n%{size_header},%{size_download}" -X POST --user "$JENKINS_USER:$JENKINS_TOKEN" "${JENKINS_JOB_URL}/${JENKINS_JOB_NAME}/build")
header_size=$(sed -n '$ s/^\([0-9]*\),.*$/\1/ p' <<< "${response}")
headers="${response:0:${header_size}}"
if ! echo ${headers} | grep -q "HTTP/2 201"; then exit 1; fi
response=$(curl -si -w "\n%{size_header},%{size_download}" -X POST --user "$JENKINS_USER:$JENKINS_TOKEN" "${JENKINS_JOB_URL}/${JENKINS_JOB_NAME}/buildWithParameters?BRANCH=${branch_name}")
header_size=$(sed -n '$ s/^\([0-9]*\),.*$/\1/ p' <<< "${response}")
headers="${response:0:${header_size}}"
if ! echo ${headers} | grep -q "HTTP/2 201"; then exit 1; fi
Jenkinsのジョブが完了するまで待機
Jenkinsのジョブを実行するとtarget queue id
という値が返ってきます。これがJenkinsがジョブに付与するグローバルな管理用IDです。下記のAPIを使うことで該当するジョブが完了したかいなか確認できます。下のコードはポーリングの一例になります。
target_queue_id=$(sed -n 's/^.*item\/\([0-9]*\).*$/\1/ p' <<< "${headers}")
while ! curl --silent --globoff --user "$JENKINS_USER:$JENKINS_TOKEN" "${JENKINS_JOB_URL}/${JENKINS_JOB_NAME}/api/xml?tree=builds[id,number,result,queueId]&xpath=//build[queueId=$target_queue_id]" | grep \<result\> > /dev/null; do echo "Waiting..."; sleep 15s; done;
ジョブの成否を読み取る
ポーリングが終わったらポーリングと同じAPIを通してジョブの結界を読み取ることができます。
ジョブの成否をGitHub Actionsに反映するために結果をリターンコードに変換します。
result=$(curl --silent --globoff --user "$JENKINS_USER:$JENKINS_TOKEN" "${JENKINS_JOB_URL}/${JENKINS_JOB_NAME}/api/xml?tree=builds[id,number,result,queueId]&xpath=//build[queueId=$target_queue_id]")
status=$(sed -n 's/^.*<result>\([A-Z]*\)<\/result>.*$/\1/ p' <<< "${result}")
if [ "$status" == "SUCCESS" ]; then exit 0; else exit 1; fi
-
GitLab CI/CD, https://docs.gitlab.com/ee/ci/ ↩
-
The .gitlab-ci.yml file, https://docs.gitlab.com/ee/ci/yaml/gitlab_ci_yaml.html ↩
-
Predefined variables reference, https://docs.gitlab.com/ee/ci/variables/predefined_variables.html ↩
-
only / except, https://docs.gitlab.com/ee/ci/yaml/#only--except ↩
-
dependencies, https://docs.gitlab.com/ee/ci/yaml/#dependencies ↩
-
artifacts:reports:dotenv, https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportsdotenv ↩
-
needs:artifacts, https://docs.gitlab.com/ee/ci/yaml/#needsartifacts ↩
-
dependencies, https://docs.gitlab.com/ee/ci/yaml/#dependencies ↩