python-gitlabでGitLabのパイプライン状況を一覧表示するHTMLをGitLab Pagesにデプロイする

# はじめに

GitLabのサブグループ以下にある複数プロジェクトのCI/CDパイプラインの実行状況を一覧化したいなーと思ったときのメモ。

# やりたいこと

GitLab CI/CDパイプライン上でGitLab APIを叩いて、結果をPagesにデプロイします。
このとき、APIの結果を集計したかったので、`python-gitlab`を使用します。

# 環境

GitLab.comで構築・実行

# やったこと

用意したのは以下の3つのファイル。
- `monitor.py` ... GitLab APIを叩いて、HTMLを作成
- `.gitlab-ci.yml` ... パイプライン定義
- `Dockerfile` ... `monitor.py`を動かすcontainer imageの定義（なくてもいい）

```monitor.py
import gitlab
import pandas as pd
import datetime
from zoneinfo import ZoneInfo
import os

GITLAB_URL = os.getenv('CI_SERVER_URL')
TOKEN = os.getenv('TOKEN')
GITLAB_GROUP_ID = os.getenv('GITLAB_GROUP_ID')

html_template = '''
<html>
<head>
<meta charset="utf-8">
<title>GitLab Pipeline Monitor</title>
</head>
<body>
<h1>GitLab Pipeline Monitor</h1>
GitLab URL: {GITLAB_URL} <br>
GitLab Group: {GITLAB_GROUP} <br>
Updated: {UPDATED_AT} <br>
Summary:
{SUMMARY}
<hr>
{TABLE}
</body>
</html>
'''


def main():
    attr = ['project_id', 'project_name', 'pipeline_updated_at', 'pipeline_status']
    gl = gitlab.Gitlab(url=GITLAB_URL, private_token=TOKEN)
    df_all = pd.DataFrame(columns=attr)
    gl_group = gl.groups.get(id=GITLAB_GROUP_ID)
    dt_now = datetime.datetime.now(ZoneInfo("Asia/Tokyo")).strftime('%Y年%m月%d日 %H:%M:%S')
    for i in gl_group.projects.list(iterator=True):
        data = {x: '' for x in attr}
        data['project_id'] = [i.id]
        data['project_name'] = [i.name]
        pipelines = gl.projects.get(i.id).pipelines.list(order_by='updated_at', sort='desc')
        if len(pipelines) == 0:
            data['pipeline_updated_at'] = ['not run']
            data['pipeline_status'] = ['not run']
        else:
            latest_pipeline = pipelines[0]
            data['pipeline_updated_at'] = [latest_pipeline.updated_at]
            data['pipeline_status'] = [latest_pipeline.status]
        df_tmp = pd.DataFrame(data, columns=attr)
        df_all = pd.concat([df_all, df_tmp], axis=0)
    df_counts = pd.DataFrame(df_all['pipeline_status'].value_counts())
    html = html_template.format(
        GITLAB_URL=GITLAB_URL,
        GITLAB_GROUP=gl_group.name,
        UPDATED_AT=dt_now,
        SUMMARY=df_counts.to_html(),
        TABLE=df_all.to_html(index=False))

    with open('index.html', mode='w') as f:
        f.write(html)


if __name__ == '__main__':
    main()
```

環境変数は以下の2つ。
- `TOKEN` ... GitLabのパーソナルトークン。
- `GITLAB_GROUP_ID` ... 取得対象のサブグループID

処理内容ですが、
`gl = gitlab.Gitlab(url=GITLAB_URL, private_token=TOKEN)`
でGitLabインスタンスを作成。
`gl_group = gl.groups.get(id=GITLAB_GROUP_ID)`
で対象のサブグループを取得しています。
`gl_group.projects.list(iterator=True)`
でサブグループ以下のプロジェクトを全量取得してループしています。
ここで、[公式ドキュメント](https://python-gitlab.readthedocs.io/en/stable/api-usage.html)にも記載されていますが、`iterator=True`あるいは`get_all=True`をオプションで指定しないと、全プロジェクト取得できないところが注意。

あとは、結果を`pandas`の`DataFrame`に入れて集計、整理して`to_html()`でHTMLとして取得しています。



```.gitlab-ci.yml
stages:
    - build
    - monitor
    - deploy

variables:
    IMAGE: ${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-pipeline-monitor

docker-build:
    stage: build
    image: docker:19.03
    services:
        - name: docker:19.03-dind
    only:
        - tags
    script:
        - docker login -u $CI_REGISTRY_USER -p $CI_JOB_TOKEN $CI_REGISTRY
        - docker build -t ${IMAGE} .
        - docker images
        - docker push ${IMAGE}:latest

monitor:
    stage: monitor
    image: ${IMAGE}:latest
    only:
        - web
        - schedule
    script:
        - python monitor.py
    artifacts:
        paths:
            - ./index.html

pages:
    stage: deploy
    only:
        - web
        - schedule
    script:
        - mkdir public
        - mv ./index.html public/
        - ls public/
    artifacts:
        paths:
            - public
```

CI/CDパイプライン定義については大したことはしておらず、`monitor.py`で出力された`index.html`を`pages`ジョブでGit Lab Pagesとしてデプロイしています。
GitLab Pagesは`pages`というジョブ名、`deploy`というステージにする必要があるところだけ注意です。


```Dockerfile
FROM python:3.11
RUN pip install --no-cache-dir python-gitlab==3.13.0
RUN pip install --no-cache-dir pandas==1.5.3
```

Dockerfileはこのくらいならなくてもいいですし、本来`requirements.txt`とかを用意しておくのが普通かと思いますが、2つだけだしいっか、ということで、少し雑です。


# 結果

パイプラインで実行した結果、Pagesにデプロイできました。

![スクリーンショット 2023-02-23 21.09.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329888/36da3ff0-7b17-92f5-5440-224e9e30e8fe.png)


![スクリーンショット 2023-02-23 21.08.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329888/2332da8d-eaef-83e9-7bc3-2e1a401319d9.png)


# まとめ

python-gitlabでGitLabのパイプライン状況を一覧表示するHTMLをGitLab Pagesにデプロイしてみました。
パイプラインをスケジュール実行すれば、定期的に状況が可視化できるかと。
なお、ソースコードは[GitLab.com](https://gitlab.com/dyam-public/gitlabpipelinemonitor)で公開しています。
ご参考までに。