5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

GitLab CI/CDのPipelinesからJenkinsのCIジョブを利用する

Posted at

はじめに

GitLabでJenkinsのジョブを実行し結果を読み取りたかったのでGitLabのCI/CD機能とJenkinsを統合を試してみました。
GitLabはGitHub Actionsと似たようなGitLab CI/CD1という機能を備えています。一般的なユースケースではないため、かなり苦労したのでハマったポイントなどをまとめた物を投稿します。

GitHub ActionsとJenkinsを統合した時の記事はこちらをご覧ください
GitHub ActionsからJenkinsのCIジョブを利用する

目的

  • GitLabのCIツールでJenkinsのジョブを作動させ、結果を読み込む。
  • MRの内容によって違うジョブを作動させたい。

利益

  • MRを作成するとき、自動でテストが行われ、その結果がMRに記される。
  • GitLabからJenkinsの細かな機能が利用できる。
  • GitLab上でPipelineの流れが把握できる。
    例1.png

限界

  • 結局のところ、JenkinsのAPIをポーリングするだけ。

GitLabのCIツールの基本的な使い方

GitLabはプロジェクトディレクトリにある.gitlab-ci.ymlというYMALファイルを認識します2。基本的にはステージとジョブを宣言するだけでpipelineが成立します。ジョブの記述は基本的にシェル環境で実行するコマンドを順に並べる感じです。

.gitlab-ci.yml
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スクリプト

全体像

.gitlab-ci.yml
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で提供される情報に違いが出ますのでこれをスクリプトの方で処理する必要があります。

only
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。この設定は実際のジョブの実行順序に影響します。また始めのスクリンショットのように依存関係のグラフを生成する時も利用されます。

needs
Testing: 
  needs: ["Check MR", "Building"]

ジョブ同士で環境変数を渡したい

dotenvルールに沿って作成されたファイルをartifactsで指定することで次のジョブの環境変数を設定することができます6。当然、このオプションを使う時はneeds:artifacts7dependencies8による依存関係の設定が必須になります。

artifacts
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
  1. GitLab CI/CD, https://docs.gitlab.com/ee/ci/

  2. The .gitlab-ci.yml file, https://docs.gitlab.com/ee/ci/yaml/gitlab_ci_yaml.html

  3. Predefined variables reference, https://docs.gitlab.com/ee/ci/variables/predefined_variables.html

  4. only / except, https://docs.gitlab.com/ee/ci/yaml/#only--except

  5. dependencies, https://docs.gitlab.com/ee/ci/yaml/#dependencies

  6. artifacts:reports:dotenv, https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportsdotenv

  7. needs:artifacts, https://docs.gitlab.com/ee/ci/yaml/#needsartifacts

  8. dependencies, https://docs.gitlab.com/ee/ci/yaml/#dependencies

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?