PhoenixをGitHub ActionsでCI/CD: CI編

# 概要

Phoenixアプリケーションを**CI/CD**する方法を学んだので、記事としてまとめます。  

本記事では、PhoenixアプリケーションCI/CDのうちCIの実装方法を解説します。  

今回は、基本的なCIの構築にとどめます。   

また、今回は別の記事で紹介したGCPの構成のデプロイを見据えて、データベース（Cloud SQL）は使用せず、簡単なCIを実装する形で進めます。

また記事中の内容は下記のリポジトリで公開しています

https://github.com/BlackTanuTanuNinjas/turorial_github_actions

以下、簡単な用語解説を行います。

## Github Actionsとは

**GitHub Actions**は、リポジトリのイベント(pushやpull request)をトリガーにして、  
テストやデプロイなどの処理を自動で実行できる**CI/CDツール**です。

## CIとは

**CI**とは、**継続的インテグレーション**(**Continuous Integration**)の略です。

またCIは、<font color="red">コードの変更が既存のコードと正しく統合されることを保証するための自動化プロセス</font>を指します。 

具体的には、開発者がコードを変更した際に、統一性を保ち、安全に取り込めるようにする仕組みです。  

そのために、CIでは次の処理を自動で行います。

* **コードの品質チェック**
    * フォーマットや静的解析を行い、コードのスタイルや記述の統一性を確認
* **コードのテスト**
    * 変更による不具合がないかを検証し、期待通り動作することを確認

CIを導入することで、開発のスピードとコードの正確性を向上させることができます。

# 環境

本記事では、以下の環境で検証を行いました。

- **OS**: macOS 15.3.1
- **Elixir**: 1.18.2 (OTP 27)
- **Erlang**: 27.2.2 

# 手順

PhoenixアプリケーションのCI/CDを構築するために、GitHub Actionsのワークフローテンプレートを利用し、カスタマイズします。

ワークフローを作成する流れは以下の通りです。

1. [リポジトリの作成](#リポジトリの作成)
2. [GitHub Actionsのワークフローテンプレートを取得](#github-actionsのワークフローテンプレートを取得)
3. [テンプレートの修正](#テンプレートの修正)

## リポジトリの作成

リポジトリを作成し、次のコマンドでPhoenixプロジェクトを作成して、リポジトリにpushしてください

```bash
mix phx.new tutorial_github_actions --no-ecto
```

また、今後の作業で使用するElixirのイメージを確認しやすくするため、Dockerfileを生成しておきます。

```bash
mix phx.gen.release --docker
```

さらに、CIでコードの静的解析を行うために、Credoを依存に追加してください。

## GitHub Actionsのワークフローテンプレートを取得

PhoenixのCIのワークフローを作成するために、**Elixirのワークフローテンプレート**を利用します。

リポジトリの「Actions」タブから、次の手順でワークフローを作成してください。

1. 「Actions」タブを開く
2. テンプレート一覧から「Elixir」を選択し、「Configure」ボタンを押す
3. ファイル名を`ci.yaml`に設定し、内容を変更せずに「Commit changes」を押す

この時点では、GitHub Actionsがエラーを検出しますが、後ほど修正するため問題ありません。

コミット後、以下のディレクトリに `ci.yaml` が生成されます。

```bash
 % tree .github 
.github
└── workflows
    └── ci.yaml
```

次の修正作業へ進む前に、ローカルリポジトリを最新の状態に更新してください。

生成されたワークフローの`on`設定により、mainブランチにpushまたはpull requestがあったときに自動で実行されます。
  

## テンプレートの修正

Elixirのワークフローテンプレートは、そのままではPhoenixアプリケーションのCIを実行できません。

そのため、次の修正を行います。

* [Elixirの公式コンテナイメージを指定](#elixirの公式コンテナイメージを指定)
* [不要なElixirのセットアップを削除](#不要なelixirのセットアップを削除)
* [Phoenixの依存関係とHexをインストール](#phoenixの依存関係とhexをインストール)
* [フォーマットのチェックを追加](#フォーマットのチェックを追加)
* [Credoのチェックを追加](#credoのチェックを追加)
* [厳密なコードチェックを追加](#厳密なコードチェックを追加)


まず、生成された `ci.yaml` は以下のようになっています。

```yaml:.github/workflows/ci.yml
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

name: Elixir CI

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

permissions:
  contents: read

jobs:
  build:

    name: Build and test
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    - name: Set up Elixir
      uses: erlef/setup-beam@61e01a43a562a89bfc54c7f9a378ff67b03e4a21 # v1.16.0
      with:
        elixir-version: '1.15.2' # [Required] Define the Elixir version
        otp-version: '26.0'      # [Required] Define the Erlang/OTP version
    - name: Restore dependencies cache
      uses: actions/cache@v3
      with:
        path: deps
        key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
        restore-keys: ${{ runner.os }}-mix-
    - name: Install dependencies
      run: mix deps.get
    - name: Run tests
      run: mix test
```

### Elixirの公式コンテナイメージを指定

GitHub Actionsのワークフローで、<font color="red">**実行環境のElixirバージョンを統一するため**</font>、  
`container: image:` を追加し、**Elixirの公式コンテナイメージ** を指定します。

```yaml:.github/workflows/ci.yml
    # 略
    runs-on: ubuntu-latest

    # 追加
    container:
      image: hexpm/elixir:1.18.2-erlang-27.2.2-debian-bullseye-20250203-slim

    # 略
```

指定するElixirのイメージ名は、生成したDockerfileから取得できます

```dockerfile:Dockerfile
ARG ELIXIR_VERSION=1.18.2
ARG OTP_VERSION=27.2.2
ARG DEBIAN_VERSION=bullseye-20250203-slim

ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
```

### 不要なElixirのセットアップを削除

先ほど、Elixirの公式コンテナイメージを指定しました。

そのため、ワークフロー内でElixirのセットアップを行う必要がなくなります。  

次のステップを削除してください。

```diff_yaml:.github/workflows/ci.yml
-    - name: Set up Elixir
-      uses: erlef/setup-beam@61e01a43a562a89bfc54c7f9a378ff67b03e4a21 # v1.16.0
-      with:
-        elixir-version: '1.15.2' # [Required] Define the Elixir version
-        otp-version: '26.0'      # [Required] Define the Erlang/OTP version
```

### Phoenixの依存関係とHexをインストール

Elixirの公式コンテナイメージには、**Phoenixの実行に必要なパッケージやHexが含まれていません**。  

そのため、次のステップを追加し、必要なツールをインストールします。

```yaml:.github/workflows/ci.yml
      - name: Install Dependency Packages
        run: |
          apt-get update -y && apt-get install -y build-essential inotify-tools git

    - name: Setup Elixir Tools
        run: |
          mix local.rebar --force
          mix local.hex --force
          mix deps.get
```

### フォーマットのチェックを追加

CIの要件には、コードのフォーマットが最適化されていることも含まれます。

そのため、フォーマットチェックを追加し、コードが`mix format`によってフォーマット済みの状態になっているかを検証します。

以下のステップを追加してフォーマットチェックを追加してください。

```yaml:.github/workflows/ci.yml
    - name: Check Code Format
        run: mix format --check-formatted
```

### Credoのチェックを追加

CI において**コードの潜在的な問題を検出するため**に、Credoを使用した静的解析を追加します。

以下のステップを追加してCredoによるチェックを追加してください。

```yaml:.github/workflows/ci.yml
    - name: Credo Checks
        run: |
            mix credo
```

### 厳密なコードチェックを追加

デフォルトのワークフローでは、以下のように `mix test` を実行しています。

```yaml:.github/workflows/ci.yml
    - name: Run tests
      run: mix test
```

しかし、CIの要件として、不具合を早期に洗い出すためのチェックが求められます。

そのため、以下のように修正し、コンパイル時およびテスト実行時の警告をエラーとして扱うようにします。

```yaml:.github/workflows/ci.yml
    - name: Run tests with warnings as errors
        run: |
          mix deps.compile
          mix compile --warnings-as-errors
          mix test --warnings-as-errors
```

### 最終的なワークフロー

今回の修正を反映した、最終的なGitHub Actionsワークフローは以下の通りです。

```yaml:.github/workflows/ci.yml
name: Elixir CI

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

permissions:
  contents: read

jobs:
  build:
    name: Build and test
    runs-on: ubuntu-latest

    container:
      image: hexpm/elixir:1.18.2-erlang-27.2.2-debian-bullseye-20250203-slim

    steps:
      - uses: actions/checkout@v4

      - name: Restore dependencies cache
        uses: actions/cache@v3
        with:
          path: deps
          key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
          restore-keys: ${{ runner.os }}-mix-

      - name: Install Dependency Packages
        run: |
          apt-get update -y && apt-get install -y build-essential inotify-tools git

      - name: Setup Elixir Tools
        run: |
          mix local.rebar --force
          mix local.hex --force
          mix deps.get

      - name: Check Code Format
        run: mix format --check-formatted

      - name: Credo Checks
        run: |
          mix credo

      - name: Run tests with warnings as errors
        run: |
          mix deps.compile
          mix compile --warnings-as-errors
          mix test --warnings-as-errors
```

# まとめ

本記事では、PhoenixアプリケーションにCIを導入する方法を解説しました。  
要点を簡単に振り返ると、次のようなことを行いました。

* **ワークフローのテンプレートを取得して設定を修正**
* **CI環境の実行方法を変更**
* **必要なパッケージを追加**
* **コードのフォーマットとテストを自動化の追加**

これにより、mainブランチへのpushやpull request時に、自動でコードの整合性をチェックできる環境が整いました。

今回はmainブランチのCIのみを扱いましたが、`.github/workflows/*.yaml` に複数のワークフローを作成することで、**ブランチごとに異なる自動化処理を設定** できます。

この仕組みを利用して、次回は **CDの実装** を行います