ポートフォリオで使ったCircleCIファイルの中身を説明してみた

31歳未経験からwebエンジニアへの転職を目指している@naota7118と申します。
この記事は[『CircleCI Advent Calendar 2020』](https://qiita.com/advent-calendar/2020/circleci)の16日目の記事です。

# はじめに
[転職活動用ポートフォリオ](https://qiita.com/naota7118/items/d641eae70dd65412a00b)でCircleCIを使いました。使う前から「CircleCIは難しそう」というイメージを持っていましたが、実際使ってみると、「使うだけならそんなに難しくないが、ちゃんと細かい部分まで理解するのは難しい」というのが正直な感想です。「使うだけならそんなに難しくない」理由は「Rails CircleCI」などで検索すれば、先人の記事がいくつも出てきて、それらを参考にすれば設定できてしまうからです。本記事では、一応使っているけれどほとんど理解できていないCircleCIの設定ファイルの中身を調べてわかる範囲で説明してみたいと思います。

# 想定する読者
これからポートフォリオでCircleCIを使おうと思っている実務未経験の方

# CircleCIとは
CI/CDサービスの1つ。GitHubのリポジトリへプッシュするタイミングで、アプリのビルドやテスト、デプロイなどの処理が実行できます。

CircleCIの設定ファイルに記述しておけば、Rspecを使ったテストやCapistranoを使った自動デプロイが`$ git push`したタイミングで自動で実行されます。

# CircleCIの設定ファイルのソースのコード
CircleCIの設定は`.circleci/config.yml`というYAMLファイルで行います。

```ruby:.circleci/config.yml
version: 2

jobs:
  build:
    docker:
      - image: circleci/ruby:2.5.1-node-browsers
        environment:
          - BUNDLER_VERSION: 2.1.4
          - RAILS_ENV: 'test'

      - image: circleci/mysql:5.6
        environment:
          - MYSQL_ALLOW_EMPTY_PASSWORD: 'true'
          - MYSQL_ROOT_HOST: '%'

    working_directory: ~/projects/pfc-master

    steps:
      - checkout
  
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "Gemfile.lock" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

      - run:
          name: install dependencies
          command: |
            gem install bundler -v 2.1.4
            bundle install --jobs=4 --retry=3 --path vendor/bundle
      - save_cache:
          paths:
            - ./vendor/bundle
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}

      - run: mv config/database.yml.ci config/database.yml 

      # Database setup
      - run: bundle exec rake db:create
      - run: bundle exec rake db:schema:load

      # - run:
      #     name: run rubocop
      #     command: bundle exec rubocop --auto-gen-config

      # run tests!
      - run:
          name: run tests
          command: |
            mkdir /tmp/test-results
            TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \
              circleci tests split --split-by=timings)"
            sudo gem install bundler
            sudo gem install rspec
            sudo gem install rspec-core
            bundle exec rspec \
              --format progress \
              --format RspecJunitFormatter \
              --out /tmp/test-results/rspec.xml \
              --format progress \
              $TEST_FILES
      # collect reports
      - store_test_results:
          path: /tmp/test-results
      - store_artifacts:
          path: /tmp/test-results
          destination: test-results

      - add_ssh_keys:
          fingerprints:
            - "90:a0:74:73:63:66:9b:9c:c2:95:9e:54:1a:2c:1d:6a"

      # - deploy:
      #     name: Capistrano deploy
      #     command: |
      #       if [ "${CIRCLE_BRANCH}" != "master" ]; then
      #       exit 0
      #       fi
      #       bundle exec cap production deploy
```

# CircleCIの設定ファイルを説明してみる
### version
```ruby
#この設定ファイルがCircleCI 2.0対応と宣言している
version: 2
```
バージョンは2、2.0、2.1のどれかを指定します。バージョンによって使用できるキーが変わってくるため、バージョンを指定する必要があります。例えば`orbs`、`commands`、`parameters`、`executors`はバージョン2.1が必須です。

### jobs
```ruby
jobs: # 実行するジョブを指定する
  build: # 「build」という名前のジョブを定義
```
ジョブとは、ある特定の目的を達成するためのプログラムのかたまりのことです。今回は「build」という名前のジョブを定義しました。

### docker

```ruby
docker: #ジョブのステップの実行環境としてDockerイメージを定義
  - image: circleci/ruby:2.5.1-node-browsers #使用するカスタムDockerイメージの名前
    environment: #環境変数の名前と値のマップ
      - BUNDLER_VERSION: 2.1.4
      - RAILS_ENV: 'test'

  - image: circleci/mysql:5.6 #使用するカスタムDockerイメージの名前
    environment: #環境変数の名前と値のマップ
      - MYSQL_ALLOW_EMPTY_PASSWORD: 'true'
      - MYSQL_ROOT_HOST: '%'
```
マップとは、関連づけること。「BUNDLER_VERSION」と「2.1.4」を関連づけている。

### working_directory

```ruby
working_directory: ~/projects/pfc-master #ステップを実行するディレクトリを定義
```

### steps 

```ruby
steps:
  - checkout
```

ステップは、ジョブ中に実行される実行可能なコマンドの集まりです。コードをチェックアウトするには checkout: キーを指定します。また、run: キーを使用すると、複数行にわたる任意のシェルコマンドスクリプトを追加できます。チェックアウトは、アプリケーションのソースコードをcloneすることを意味します。

### restore_cache
```ruby
- restore_cache:
  keys:
    - v1-dependencies-{{ checksum "Gemfile.lock" }}
    # fallback to using the latest cache if no exact match is found
    - v1-dependencies-
```

restore_cacheは以前に保存したキャッシュをkeyに基づいて復元します。あらかじめsave_cacheステップを使用して、そのキーでキャッシュを保存しておく必要があります。

キャッシュとは、ウェブページの情報を一時的に保存する仕組みのことです。キャッシュという一時的な保存データがスマホやPC内にあることで、再表示の速度が上がります。

### run
```ruby
- run: #runキーは任意のコマンドの呼び出し
  name: install dependencies　#CircleCIのUIに表示されるステップのタイトル (デフォルトは command 文字列全体)。
  command: | #シェルから実行するコマンド。
    gem install bundler -v 2.1.4
    bundle install --jobs=4 --retry=3 --path vendor/bundle
```
それぞれのrun宣言で新しいシェルが立ち上がります。複数行のcommandを指定でき、その場合はすべての行が同じシェルで実行されます。runで任意のコマンドを設定することができ、`command: |`という記述方法をとることで、それ以下に複数行を書いても全て同じシェルで実行されます。今回はrunでbundlerのバージョンを指定しbundle installするコマンドの処理を任意で設定しました。

### save_cache
```ruby
- save_cache:
   paths:
     - ./vendor/bundle
   key: v1-dependencies-{{ checksum "Gemfile.lock" }}
```
ドキュメントには「依存関係やソースコードなどのファイルのキャッシュ、または複数のファイルが入ったディレクトリのキャッシュを生成して、オブジェクトストレージに格納します」とありますが、正直全然わかりません。ドキュメントには、キャッシュを説明するページがありました（下記リンク参照）。それによると、キャッシュはデータを再利用することでCircleCIのジョブを高速化してくれるとのことです。キャッシュを使用する目的がわかったので、今回は仕組みの理解は諦めます。

[ドキュメントのキャッシュの説明](https://circleci.com/docs/ja/2.0/caching/)


### run(2回目)
```ruby
- run: mv config/database.yml.ci config/database.yml 
```
runは任意のコマンドを設定できるのでした。`config/database.yml.ci`の中身を`config/database.yml `に反映させています。既存の`database.yml`を指定した結果エラーが出たため、CircleCI用のデータベース設定を別で作ることにしました。

```ruby:config/database.yml.ci
test:
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: 'root'
  port: 3306
  host: '127.0.0.1'
  database: ci_test
```

```ruby
# Database setup
- run: bundle exec rake db:create
- run: bundle exec rake db:schema:load
```
`rake db:schema:load`は`db/schema.rb`からテーブルを作成します。`rake db:schema:load`の部分を`rake db:migrate`に変更している例をちらほら見かけたので、2つを使い分ける必要がありそうです。

```ruby
# - run:
  # name: run rubocop
  # command: bundle exec rubocop --auto-gen-config
```
rubocopで落ちた部分の修正を重ねてもsuccessにならないため、今は一時的にコメントアウトしてあります。数日中になんとかしたいと思います。

### Rspec

```ruby
  - run:
     name: run tests #この名前で表示される
     command: | #複数のコマンドの1つのシェルで処理できる
       mkdir /tmp/test-results #テスト用のファイルを作成
       TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \
         circleci tests split --split-by=timings)"

       sudo gem install bundler
       sudo gem install rspec #Rspecをインストール
       sudo gem install rspec-core
       bundle exec rspec \ #Rspecによるテスト実行
         --format progress \
         --format RspecJunitFormatter \
         --out /tmp/test-results/rspec.xml \
         --format progress \
         $TEST_FILES
```

### store_test_results
```ruby
- store_test_results:
   path: /tmp/test-results
```
> store_test_resultsは、ビルドのテスト結果をアップロードおよび保存するための特別なステップです。 テスト結果は、CircleCI Webアプリケーションで各ビルドの「テストサマリー」セクションに表示されます。 テスト結果を保存すると、テストスイートのタイミング分析に役立ちます。

`/tmp/test-results`ここにテスト結果が保存されます。テストスイートという謎ワードが出てきたので調べました。タイミング分析は見つかりませんでしたが、「テストはタイミングも重要なのか」ということにします。

>テストケースとは、テストを行うエンジニアがどんなテストをすればいいか、その手順をまとめたものです。ひとつのシナリオが完結するまでのテストケースを集めたものを、テストスイートと呼びます。
引用元：https://qangaroo.jp/info/test-case-plan-do/　　

### store_test_results
```ruby
- store_artifacts:
  path: /tmp/test-results
  destination: test-results
```
>store_artifactsは、Webアプリまたは API からアクセスできるアーティファクト (ログ、バイナリなど) を格納するステップです。

アーティファクトとは、ソフトウェアを作る過程で生み出された副産物のことです。文脈によってアーティファクトが示す対象は変わってくるようです。
https://blog.jfrog.co.jp/entry/what-is-an-artifact

今回はいちばんイメージしやすいので、アーティファクト＝ログとして、ログを格納するコマンドとして理解することにします。なぜログなどの副産物を格納しておく必要があるのかを調べるのは今後の課題とします。

### add_ssh_keys
```ruby
- add_ssh_keys:
  fingerprints:
    - "90:a0:74:73:63:66:9b:9c:c2:95:9e:54:1a:2c:1d:6a"
```
>add_ssh_keysはプロジェクトの設定からコンテナにSSH鍵を追加するための特別なステップです。指定する鍵を使用するようにSSHを構成します。

SSH鍵を追加したのは、Capistranoの自動デプロイを実行するためでした。CircleCIでCapistranoの自動デプロイもしたいという方はこの設定が必要になります。私の場合はまだ自動デプロイのエラーが解決できておらず、CI/CDのCDは実現できていません。

# まとめ
まだまだ全然理解できていないのですが、ドキュメントが親切なので、ググればだいたいヒットくれます。これからも1つ1つ疑問点をつぶして理解を深めていきたいと思います。