GitLab CI/CDのPipelinesからMRを読み取り流れを変える

# はじめに

##目的

- MRのdescriptionでGitLabのCI/CDの流れを変えること

##利益

- 流れを細分化した一つのpipelineで類似しているテストをまとめて管理できます
- 文字列を解析するから直感的な記述でCI/CDをコントロールすることができます

##使用例
- マルチのリポジトリをまとめてビルドできます
- MRがブロックされてる場合ビルドをスキップできます

# PipelinesからMRを読み取り流れを変える

## シナリオ
readyステージのCheck MRというジョブでMRを読み取り以後のパイプラインで使う環境変数を設定すると仮定します。

シナリオの最終目的は
1. REPO1でMRがpipelineをトリガーした時
2. REPO1のMRに記載された依存情報を利用して
3. REPO2のMRの情報を読み取り 
4. ビルドに使用する依存先のブレンチの名前を得る事です。

## 適用例

### Merge request
![mr_example.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286572/5bf1330f-3faf-0d27-eac0-e594ae23256a.png)

### Pipelineスクリプト
```yml:.gitlab-ci.yml
variables:
  GITLAB_URL: "http://gitlab_url"
  #MRはREPO-1で作成されREPO-1はREPO-2に依存していると仮定
  PROJECT_REPO1_ID: "120" # REPO-1
  PROJECT_REPO2_ID: "121" # REPO-2

stages:
  - ready

Check MR:
  stage: ready
  only: 
    - web
    - schedule
    - merge_request
  script:
    - |-
      if [ "$CI_PIPELINE_SOURCE" == "merge_request_event" ]
      then
        echo "Initialing..."
        MR_DESCRIPTION=$(curl --silent --header "PRIVATE-TOKEN:$GITLAB_PRIVATE_TOKEN" "${GITLAB_URL}/api/v4/projects/${CI_MERGE_REQUEST_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}")
        echo "MR_DESCRIPTION=$(echo "$MR_DESCRIPTION" | python3 -c 'import sys, json; print(json.load(sys.stdin)["description"])')" >> build.env
        DEPEND_MR_IID=$(echo "$MR_DESCRIPTION" | python3 -c 'import sys,re; m=re.search(“MY_PROJECT_REPO2!
\\\{0,1}!([0-9]+)”, str(*sys.stdin)); print("-1") if m is None else print(m.group(1))')
        echo "DEPEND_MR_IID=$DEPEND_MR_IID" >> build.env
        echo "REQUEST_SOURCE_BRANCH_NAME=${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}" >> build.env
        if [ "$DEPEND_MR_IID" == "-1" ]
        then 
          echo "DEPEND_REPO2_BRANCH=master >> build.env
        else 
          echo "DEPEND_REPO2_BRANCH=$(curl --silent --header "PRIVATE-TOKEN:$GITLAB_PRIVATE_TOKEN" "${GITLAB_URL}/api/v4/projects/${PROJECT_REPO2_ID}/merge_requests/${DEPEND_MR_IID}" | python3 -c 'import sys, json; print(json.load(sys.stdin)['source_branch'])')" >> build.env
        fi
      else
        {
          echo "REQUEST_SOURCE_BRANCH_NAME=${CI_COMMIT_REF_NAME}"
          echo "DEPEND_REPO2_BRANCH=master"
        } >> build.env
      fi
  artifacts:
    reports:
      dotenv: build.env
```

## 使用している変数
### Predefined variables[^1]
- CI_PIPELINE_SOURCE：トリガーの種類。上記の例ではマージリクエストを判断するために使いました。

```bash:CI_PIPELINE_SOURCE
  script:
    - |-
      if [ "$CI_PIPELINE_SOURCE" == "merge_request_event" ]
      then
        ~~~~~~
      else
        ~~~~~~
      fi
```

- 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
- GITLAB_URL：GitLabのURL
- PROJECT_REPO1_ID：現在パイプラインが動作しているリポジトリのID。
- PROJECT_REPO2_ID：REPO1が依存しているリポジトリのID。

:::note info
PROJECT_REPO1_IDとPROJECT_REPO2_IDは二つ以上のリポジトリを使うシナリオのための変数です。単一のリポジトリを使う場合はこの二つの変数が関わっている部分は無視してください。
:::

### Project CI/CD variables
- GITLAB_PRIVATE_TOKEN：GitLab APIのためのTOKEN

### Variables in artifact "build.env" file (ステージ間の値の受け渡しのための変数)
- MR_DESCRIPTION：MRのDESCRIPTIONを読み取り保管する変数
- DEPEND_MR_IID：依存先のMRのIDを保持する変数
- REQUEST_SOURCE_BRANCH_NAME：REPO1のMRのブレンチの名前
- DEPEND_REPO2_BRANCH：REPO2のMRのブレンチの名前



## スクリプトの解説

### MRのDESCRIPTIONが読みたい
GitLabのAPI(`/api/v4/projects/${CI_MERGE_REQUEST_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}`)を叩くことでMRに関する情報をJSONの形で得ることができます。DESCRIPTIONの所はPYTHONを使って読み取ります。

```bash
MR_INFO=$(curl --silent --header "PRIVATE-TOKEN:$GITLAB_PRIVATE_TOKEN" "${GITLAB_URL}/api/v4/projects/${CI_MERGE_REQUEST_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}")
MR_DESCRIPTION=$(echo "$MR_DESCRIPTION" | python3 -c 'import sys, json; print(json.load(sys.stdin)["description"])')
```

### MRのDESCRIPTIONを解析したい
MRのdescriptionを得たのであとは文字列をどう解析して使うかの問題です。ここは決められた方法はありません。自分の目的のデスクリップションに記載されたMR間の依存情報を読み取ることです。
上記の例ではデスクリップション欄に`Depends: MY_PROJECT_REPO2!100`のように依存先のMRのリンクを記しておいてPythonで100という数字（GitLabでいうMR_IID)だけをパーシングしました。このIIDを利用してまた依存先のREPOのMRの情報を獲得できるようになります。

```bash
DEPEND_MR_IID=$(echo "$MR_DESCRIPTION" | python3 -c 'import sys,re; m=re.search(“MY_PROJECT_REPO2\\\{0,1}!([0-9]+)”, str(*sys.stdin)); print("-1") if m is None else print(m.group(1))')
```

### MRが依存している外部のリポジトリのブランチ名を知りたい
上記で読み取ったIIDを利用して今回は依存先のMRの情報を読み取ります。ここでは`source_branch`を利用してREPO２のどのブレンチを利用するべきかを決めます。

```bash
DEPEND_REPO2_BRANCH=$(curl --silent --header "PRIVATE-TOKEN:$GITLAB_PRIVATE_TOKEN" "${GITLAB_URL}/api/v4/projects/${PROJECT_REPO2_ID}/merge_requests/${DEPEND_MR_IID}" | python3 -c 'import sys, json; print(json.load(sys.stdin)['source_branch'])')
```


[^1]: Predefined variables reference, https://docs.gitlab.com/ee/ci/variables/predefined_variables.html