Django/ReactのCircleCi設定ファイル書き方

## 概要
1リポジトリに、バックエンドとフロントエンドにフォルダを分けて入れた場合のcircleci設定ファイルの書き方例を紹介します。
下記のようなディレクトリ構造
project/
　├ .circleci/
　│　└ config.yml
　├ backend/
　│　└ manage.py
　│　└ requirements.txt
　├ frontend/
　│　└ package.json
　│　└ yarn.lock
　├ is_changed_dir.sh

シェルスクリプトを使用して、バックエンドフォルダ内のファイルを修正した場合バックエンドのテスト、フロントエンドフォルダ内のファイルを修正した場合フロントエンドのテストが実行されるように設定しました。

## 構成
- バックエンド
    - Python3.9
        - Django3.1.3
    - Postgresql9.6.2
- フロントエンド
    - React17.0.1

## 全体
最初に、設定ファイル全体を記載します。

``` yaml:config.yml
version: 2.1

setup_command: &setup_command
  command: |
    sudo chmod +x is_changed_dir.sh
    echo 'export PATH=$PATH:${CIRCLE_WORKING_DIRECTORY}' >> $BASH_ENV

executors:
  backend-executor:
    docker:
      - image: circleci/python:3.9.0
        environment:
          PYTHONBUFFERED: 1
          SECRET_KEY: SECRET_KEY
          DATABASE_ENGINE: django.db.backends.postgresql
          DATABASE_HOST: localhost
          DATABASE_PORT: 5432
          POSTGRES_USER: dbuser
          POSTGRES_PASSWORD: dbpassword
          POSTGRES_DB: postgreDB
      - image: circleci/postgres:12.1-alpine
        environment:
          POSTGRES_USER: dbuser
          POSTGRES_PASSWORD: dbpassword
          POSTGRES_DB: postgreDB
  frontend-executor:
    docker:
      - image: circleci/node:14.14.0

commands:
  save_python_package:
    description: "pip install して package をキャッシュ"
    steps:
      - save_cache:
          name: Save python package
          key: deps-{{ .Branch }}-{{ checksum "backend/requirements.txt" }}
          paths:
            - /usr/local/bin
            - /usr/local/lib/python3.9/site-packages
  restore_python_package:
    description: "package キャッシュを読み込む"
    steps:
      - restore_cache:
          name: Restore Python package
          keys: 
            - deps-back-{{ .Branch }}-{{ checksum "backend/requirements.txt" }}
            - deps-back-
  pip_install:
    description: "pip install を実行"
    steps:
      - run:
          name: pip install
          command: pip3 install -r backend/requirements.txt
  save_node_modules:
    description: "node_modules をキャッシュ"
    steps:
      - save_cache:
          name: Save node_modules
          key: deps-{{ .Branch }}-{{ checksum "frontend/yarn.lock"}}
          paths:
            - frontend/node_modules
  restore_node_modules:
    description: "node_modules を読み込む"
    steps:
      - restore_cache:
          name: Restore node_modules
          keys:
            - deps-front-{{ .Branch }}-{{ checksum "frontend/yarn.lock"}}
            - deps-front-
  yarn_install:
    description: "yarn install を実行"
    steps:
      - run:
          working_directory: frontend
          name: node_modules install
          command: yarn

jobs:
  backend_build:
    executor: backend-executor
    steps:
    - checkout
    - run:
        <<: *setup_command
    - run:
        name: backend diff
        command: |
          if ! is_changed_dir.sh "backend/"; then
            circleci step halt
          fi
    - run: sudo chown -R circleci:circleci /usr/local/bin
    - run: sudo chown -R circleci:circleci /usr/local/lib/python3.9/site-packages
    - restore_python_package
    - pip_install
    - save_python_package
    - run: 
        working_directory: backend
        command: python manage.py test
    - store_test_results:
        path: test-results
    - store_artifacts:
        path: test-reports
  frontend_build:
    executor: frontend-executor
    steps:
    - checkout
    - run:
        <<: *setup_command
    - run:
        name: frontend diff
        command: |
          if ! is_changed_dir.sh "frontend/"; then
            circleci step halt
          fi
    - run: sudo chown -R circleci:circleci /usr/local/lib/node_modules
    - restore_node_modules
    - yarn_install
    - save_node_modules
    - run: 
        working_directory: frontend
        command: yarn test
    - store_test_results:
        path: test-results
    - store_artifacts:
        path: test-reports

workflows:
  version: 2.1
  backend-test:
    jobs:
    - backend_build
  frontend-test:
    jobs:
    - frontend_build
```
## 各種説明
#### シェルスクリプトのセットアップ
特定ディレクトリ以下の変更を検知するシェルスクリプトは、下記記事を参考とさせていただきました。
参考：(https://qiita.com/sensuikan1973/items/2f7d04e7ce93305dd2f4)

``` yaml
# シェルスクリプトに実行権限の付与、パスを通してます
setup_command: &setup_command
  command: |
    sudo chmod +x is_changed_dir.sh
    echo 'export PATH=$PATH:${CIRCLE_WORKING_DIRECTORY}' >> $BASH_ENV
```
#### 特定ディレクトリ以下の変更検知
シェルスクリプトのセットアップを実行してから、シェルスクリプトの引数に検知したいディレクトリパスを渡して変更チェックを行います。変更されていない場合は、`circleci step halt`で処理を終了しています。

``` yaml
    steps:
    - checkout
    - run:
        <<: *setup_command
    - run:
        name: backend diff
        command: |
          if ! is_changed_dir.sh "backend/"; then
            circleci step halt
          fi
```
#### executor
実行環境としては、バックエンドとフロントエンドを別々に準備しています。
バックエンドの環境変数は、`setting.py`内で`os.environ.get('DATABASE_ENGINE')`のように取得して利用しています。

```yaml
executors:
  backend-executor:
    docker:
      - image: circleci/python:3.9.0
        environment:
          PYTHONBUFFERED: 1
          SECRET_KEY: SECRET_KEY
          DATABASE_ENGINE: django.db.backends.postgresql
          DATABASE_HOST: localhost
          DATABASE_PORT: 5432
          POSTGRES_USER: dbuser
          POSTGRES_PASSWORD: dbpassword
          POSTGRES_DB: postgreDB
      - image: circleci/postgres:9.6.2-alpine
        environment:
          POSTGRES_USER: dbuser
          POSTGRES_PASSWORD: dbpassword
          POSTGRES_DB: postgreDB
  frontend-executor:
    docker:
      - image: circleci/node:14.14.0
```
#### command
コマンド定義としては、パッケージのキャッシュ、リストア、インストール処理をバックエンドとフロントエンドで定義を行っています。
`yarn`については、`frontend`フォルダで実行しないと行けないため、`working_directory`を設定しています。

``` yaml
commands:
  save_python_package:
    description: "pip install して package をキャッシュ"
    steps:
      - save_cache:
          name: Save python package
          key: deps-{{ .Branch }}-{{ checksum "backend/requirements.txt" }}
          paths:
            - /usr/local/bin
            - /usr/local/lib/python3.9/site-packages
  restore_python_package:
    description: "package キャッシュを読み込む"
    steps:
      - restore_cache:
          name: Restore Python package
          keys: 
            - deps-back-{{ .Branch }}-{{ checksum "backend/requirements.txt" }}
            - deps-back-
  pip_install:
    description: "pip install を実行"
    steps:
      - run:
          name: pip install
          command: pip3 install -r backend/requirements.txt
  save_node_modules:
    description: "node_modules をキャッシュ"
    steps:
      - save_cache:
          name: Save node_modules
          key: deps-{{ .Branch }}-{{ checksum "frontend/yarn.lock"}}
          paths:
            - frontend/node_modules
  restore_node_modules:
    description: "node_modules を読み込む"
    steps:
      - restore_cache:
          name: Restore node_modules
          keys:
            - deps-front-{{ .Branch }}-{{ checksum "frontend/yarn.lock"}}
            - deps-front-
  yarn_install:
    description: "yarn install を実行"
    steps:
      - run:
          working_directory: frontend
          name: node_modules install
          command: yarn
```
#### jobs
フロントエンドとバックエンドのテストを定義しています。
基本的に以下の流れで処理を行っています。
1. チェックアウト
2. コマンドセットアップ
3. 変更検知処理
4. パッケージリストア、インストール、キャシュ
5. テスト実行
6. テスト結果保存

```yaml
jobs:
  backend_build:
    executor: backend-executor
    steps:
    - checkout
    - run:
        <<: *setup_command
    - run:
        name: backend diff
        command: |
          if ! is_changed_dir.sh "backend/"; then
            circleci step halt
          fi
    - run: sudo chown -R circleci:circleci /usr/local/bin
    - run: sudo chown -R circleci:circleci /usr/local/lib/python3.9/site-packages
    - restore_python_package
    - pip_install
    - save_python_package
    - run: 
        working_directory: backend
        command: python manage.py test
    - store_test_results:
        path: test-results
    - store_artifacts:
        path: test-reports
  frontend_build:
    executor: frontend-executor
    steps:
    - checkout
    - run:
        <<: *setup_command
    - run:
        name: frontend diff
        command: |
          if ! is_changed_dir.sh "frontend/"; then
            circleci step halt
          fi
    - run: sudo chown -R circleci:circleci /usr/local/lib/node_modules
    - restore_node_modules
    - yarn_install
    - save_node_modules
    - run: 
        working_directory: frontend
        command: yarn test
    - store_test_results:
        path: test-results
    - store_artifacts:
        path: test-reports
```

#### workflows
単純に、jobsで設定したバックエンドとフロントエンドのテストジョブを実行しています。

```yaml
workflows:
  version: 2.1
  backend-test:
    jobs:
    - backend_build
  frontend-test:
    jobs:
    - frontend_build
```

#### 最後に
簡単に紹介しましたが、今回はまだ自動テストのみなのでPRされたブランチに応じた自動デプロイなどを組み込めたらまた簡単に紹介したいと思います。