Gitlab-CIでテスト結果をブラウザ上で表示できるようにする

社内ではプロジェクトの管理をGitlabで行っていますが、このGitlabにはテストを実行するGitlab-CIという機能があります。
これは、TravisCIやCircleCIと類似のCIツールでDockerを使ったテストなどを行うことができます。
テストの実際の処理はGitlab runnerと呼ばれるテスト実行するノードで行います。このRunnerは手軽に構築でき適宜増やすことができます。

このGitlab-CIはとても便利なのですが、HTMLとして生成したテスト結果やカバレッジのレポートをブラウザ上で表示できないという問題があります。
テストで生成したファイルを保存しておく`artifacts`という機能はあるのですが、これで保持したファイルのなかでhtmlやtext形式などのファイルは直接見ることができません。

そこで、GitlabのReview Appを使って生成したHTMLを表示できるようにする方法を考えました。

* Introducing Review Apps | GitLab
  * https://about.gitlab.com/2016/11/22/introducing-review-apps/


## 実現方法

具体的な環境の構築方法や使い方は以下の通りです。

*Webサーバー環境構築*

1. レポート結果のHTMLを提供するためのサーバーを用意する
2. そのサーバー上にnginxで提供ファイルを配置するディレクトリをドキュメントルートとしたhttpサーバーを構築する
3. そのサーバー上にshellのgitlab runnerを構築する
4. nginxのドキュメントルートにGitlab runnerのユーザ(デフォルトでgitlab-runnerユーザ)で書き込みできるようにする


*Gitlab-CIのジョブ定義*

Gitlab-CIはyamlでテストの処理内容を定義します。
テスト結果をwebで公開するには以下のようなジョブの定義にします。

```yaml:.gitlab-ci.yml
stages:
  - test
  - deploy_report

test:
  stage: test
  script:
    # テストを実行
    - do some test
  artifacts:
    paths:
      # テスト結果ファイルのあるディレクトリをartifactとして保存するように指示
      - target/test-reports/

# テストレポートを公開場所にデプロイするタスク
deploy_test_report:
  stage: deploy_report
  variables:
    # artifactsしか使わないのでgitリポジトリのcloneをしないように設定
    GIT_STRATEGY: none
  script:
    # testジョブで保存したartifactsがカレントディレクトリにすでにある
    # ファイルを公開ようにフォルダ整理
    - mv target/test-reports/ ./test-reports
    # テスト結果をweb公開ディレクトリに移す
    - rsync -av --delete ./test-reports /srv/nginx/pages/$CI_BUILD_REF_NAME
  environment:
    name: review/$CI_BUILD_REF_NAME
    url: http://$CI_BUILD_REF_NAME.$APPS_DOMAIN
    on_stop: stop_review


# 公開したファイルを破棄するタスク
undeploy_test_report:
  stage: deploy_report
  variables:
    GIT_STRATEGY: none
  script:
    # 公開ディレクトリのファイルを削除
    - rm -rf public /srv/nginx/pages/$CI_BUILD_REF_NAME
  when: manual
  environment:
    name: review/$CI_BUILD_REF_NAME
    action: stop
```

上記のようにすることでテスト実行後に、nginxサーバーにテストレポートのファイルがコピーされます。
あとはそこで公開されているファイルをブラウザから閲覧することができるようになります。


## 公開ファイルのアクセス制限

前述の方法でテストレポートをブラウザから参照できるようになったのですが、１つ問題があります。
上記のwebサーバーには何らアクセス制限などがかけられておらず、URLがわかればだれでも閲覧できてしまいます。

そこで、このwebサーバーの閲覧をGitlabの各プロジェクトのアクセス権限と同じレベルで制限できる仕組みを作りました。

  * m3dev/gitlab-access-controlled-server
    * https://github.com/m3dev/gitlab-access-controlled-server

### 大まかな仕組み

GitlabはOAuth2プロバイダーサーバーとして認証機能を提供する機能があります。
この仕組みは、nginx + oauth2_proxy + 自作pythonスクリプトの組み合わせでOAuth2認証付きのサーバーとして動作します。
nginxがhttpサーバーとして動作し、リクエストをoauth2_proxyにproxyします。
oauth2_proxyは、Gitlabを使ったOAuth2認証を行い、Gitlabで認証できた場合にのみ自作pythonスクリプトでのサーバーにさらにproxyします。
自作pythonスクリプトはwebサーバーとて動作し、その中でoauth2_proxyから受け取ったOAuth2のアクセストークンを使ってGitlab APIにアクセスします。Gitlab APIで対象プロジェクトにアクセスできるかどうかを確認できれば、指定されたディレクトリかのファイルをレスポンスします。

### プロジェクト判定方法

この仕組みの中で問題になるのが、アクセスしようとしてるURLがどのプロジェクトに属したリソースであるかを判別する方法です。

はじめはURL中にプロジェクトIDを含めることを考えたのですがそれだと汎用性が低い感じがあったので、公開したいファイルのディレクトリに `.gitlab-info.json`というファイルを配置し、その中にプロジェクトIDなどを定義するようにしました。
動きとしては、自作pythonスクリプトサーバーはアクセスされたURLのパスに対応するフォルダから上記のファイルを探します。もしなければ、ドキュメントルートまで上位階層へ登りながらこのファイルを探します。
ファイルを見つけたらその中身のJSONからプロジェクトIDや権限設定を取得します。

### 構築方法

この認証機能付きサーバーの構築方法は、プロジェクト内にDockerでの構築方法を用意しているのでそれを参考にしてください。
Dockerをそのまま使うこともできると思います。

### 使い方

Gitlab-CIでのテストの使い方は、基本的には前述の内容と同じです。
違うところは、ファイルをデプロイするときに`.gitlab-info.json`ファイルを作成することを追加した点です。

```diff
 # テストレポートを公開場所にデプロイするタスク
 deploy_test_report:
   stage: deploy_report
   variables:
     # artifactsしか使わないのでgitリポジトリのcloneをしないように設定
     GIT_STRATEGY: none
   script:
     # testジョブで保存したartifactsがカレントディレクトリにすでにある
     # ファイルを公開ようにフォルダ整理
     - mv target/test-reports/ ./test-reports
     # .gitlab-info.jsonを作る
+    - echo "{\"project_id\": $CI_PROJECT_ID, \"requires_access_to_code\": false}" > public/.gitlab-info.json
     # テスト結果をweb公開ディレクトリに移す
     - rsync -av --delete ./test-reports /srv/nginx/pages/$CI_BUILD_REF_NAME
   environment:
     name: review/$CI_BUILD_REF_NAME
     url: http://$CI_BUILD_REF_NAME.$APPS_DOMAIN
     on_stop: stop_review
```


## 補足

このGitlab-CIでのテスト結果の閲覧に関しては、Gitlabの開発コミュニティ上でも対応が検討されているようです。

  * https://gitlab.com/gitlab-org/gitlab-ce/issues/10982