GitHub ActionsでCI/CD環境を学びながら構築する

## はじめに
[GitHub Actions](https://help.github.com/ja/actions)を使ってCI/CD環境を構築します。
今回の目標は[Firebase](https://firebase.google.com/)に[Gatsby](https://www.gatsbyjs.org/)で作った静的なWebサイトを自動的にビルド、デプロイする環境の構築です。

## GitHub Actionsとは
GitHubのリポジトリにCI(継続的インテグレーション)とCD(継続的デプロイメント)の機能を直接作ることができる機能。
CIについては公式ドキュメントで丁寧に解説されています。
[継続的インテグレーションについて
](https://help.github.com/ja/actions/building-and-testing-code-with-continuous-integration/about-continuous-integration)

## GitHub Actionsのことば
では早速CI/CD環境を構築していきたいのですが、私はCI/CDツールに関するリテラシーがまだまだ低いので、GitHub Actionsで登場する概念を学びつつ進めましょう。

### ワークフローとは
GitHub Actionsで言うワークフローとは、任意のプロジェクトをビルドしたり、テストしたり、デプロイしたり…という様々な作業のまとまりです。
実際にはプロジェクトのルートに`.github/workflows`というディレクトリを作り、その中に`*.yml`ファイルを作り、このファイルに具体的な処理を記述します。`*.yml`ファイルが1つのワークフローとして定義され、プロジェクトで複数のワークフローを定義できます。

### ジョブとは
ワークフローの中で実行される処理のひとまとまりをジョブと呼んでいます。ジョブはワークフローに複数定義することができ、それぞれのジョブは並行して処理されます。
また、ジョブはそれを実行するマシンを指定できます。(例えばMacとかWindowsとか)

### ステップとは
ジョブの中で実行される一連のタスク。ワークフローを構成する様々なタスクの最小単位という感じ。
`npm install`を実行する、`npm run build`を実行する、くらい具体的なタスクです。

### アクションとは
ステップとして実行されるタスクの中で再利用可能なコードの単位をアクションと呼びます。
例えば『Firbaseにデプロイする』という一連のタスクを再利用可能な形にしているもののことです。

なんとなくGitHub Actionsの概念を掴んできたところで、次は実際のワークフローを定義している`*.yml`ファイルを見てみます。結局は見たほうがわかりますしね。

## GitHub Action for Firebaseを使ってみる
GitHubのMarketplaceには様々なアクションが用意されています。今回はその中から[GitHub Action for Firebase](https://github.com/marketplace/actions/github-action-for-firebase)を使ってワークフローを作ります。

ちなみにこのときのプロジェクトはこんな構成です。

```
root
├── .github
│   └── workflows
│       └── firebase.yml
├── node_modules
├── public
│   └── ビルドされたファイルはここにできる
├── src
│   └── ソースファイルたくさん
├── .firebaserc
├── .gitignore
├── package.json
├── yarn.lock
├── その他諸々
```

このプロジェクトに、[GitHub Action for Firebase](https://github.com/marketplace/actions/github-action-for-firebase)のExampleを参考にワークフローを作成します。
↓これはExampleを私の環境で使うために少し手を加えたワークフローです。

```yaml:.github/workflows/firebase.yml
name: Build and Deploy
on:
  push:
    branches:
      - master

jobs:
  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@master
      - name: Install Dependencies
        run: npm install
      - name: Build
        run: npm run build
      - name: Archive Production Artifact
        uses: actions/upload-artifact@master
        with:
          name: public
          path: public
  deploy:
    name: Deploy
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@master
      - name: Download Artifact
        uses: actions/download-artifact@master
        with:
          name: public
          path: public
      - name: Deploy to Firebase
        uses: w9jds/firebase-action@master
        with:
          args: deploy
        env:
          FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
```

私は今回CI/CDツール初体験ですが、こうしてコードで見るとこのワークフローが何をしようとしているのか、なんとなくわかりますね。
それではこのワークフローをちゃんと理解するため、順番にコードを読んでいきましょう。

```yml:.github/workflows/firebase.yml
name: Build and Deploy
on:
  push:
    branches:
      - master
# 以下略
```
この部分はワークフローの名前と、このワークフローをトリガーするイベントが記述されています。
今回の例だと、`master`ブランチに新しいコードがプッシュされた時にトリガーされます。なんてわかりやすいんでしょう。

続いてBuildジョブの部分を読んでみます。

### Buildジョブ

```yml
jobs:
  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@master
      - name: Install Dependencies
        run: npm install
      - name: Build
        run: npm run build
      - name: Archive Production Artifact
        uses: actions/upload-artifact@master
        with:
          name: public
          path: public
```
Buildジョブがどのように定義されているか順に読んでいきます。

#### jobs.build
`build`はこのジョブを一意に特定するためのIDです。英数字、`-`、`_`のみ使えます。

#### jobs.build.name
このジョブの名前です。読んだまんまですね。たぶんこの値はそんなに重要じゃないのかなーと思います。

#### jobs.build.runs-on
このジョブを実行するマシンを定義します。
ここでMacやWindowsも選べますが、今回はUbuntuの最新版にしています。これも読んだまんまという感じです。

#### jobs.build.steps.name
ここからステップを定義していきます。まずは名前です。はい、読んだまんまですね。

#### jobs.build.steps.uses
ここでアクションを使うことができます。
`uses: actions/checkout@master`というのは、GitHubの`actions/checkout`というパブリックリポジトリのアクションの`master`ブランチのコードを使わせていただく、という意味です。
このアクションでは、チェックアウトするリポジトリや、Gitの参照を指定できるのですが、今回は何も指定していません。デフォルトで、このワークフローのあるリポジトリの`master`ブランチにチェックアウトしてくれます。

#### jobs.build.steps.run
シェルコマンドを実行できます。
シェルを明示的に指定することもできますが、デフォルトではこのジョブを実行しているマシンのシェルで実行するので、今回は明示していません。

#### 成果物をジョブ間でビルドしたファイルを受け渡す
Buildジョブではもちろんビルドするわけですが、この後デプロイするためにはこのビルドしたアプリを次のDeployジョブに受け渡す必要があります。そのためのアクションが`actions/upload-artifact`と`actions/download-artifact`です。また、このビルドして生成されたファイルを成果物と呼びます。
`npm run build`で成果物を生成し、これを`actions/upload-artifact`でGitHub上のストレージに保存します(この成果物は90日間保存されます)。

Buildジョブはこれで完了です。続いてDeployジョブも読んでみましょう。

### Deployジョブ
```yml
jobs:

  # ここにBuildジョブ

  deploy:
    name: Deploy
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@master
      - name: Download Artifact
        uses: actions/download-artifact@master
        with:
          name: public
          path: public
      - name: Deploy to Firebase
        uses: w9jds/firebase-action@master
        with:
          args: deploy
        env:
          FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
```

Buildジョブとは違う部分だけ見ていきましょう。

#### jobs.deploy.needs
ジョブが複数ある場合、それらは基本的に並行して処理されますが、ジョブが処理される順序を設定することもできます。
`jobs.deploy.needs`にジョブIDを設定すると、そのジョブが完了するまで待機させることができます。このワークフローでは、DeployジョブはBuildジョブが完了してから実行するようにしています。

#### w9jds/firebase-action
このアクションの`jobs.deploy.steps.with.args`にfirebase CLIのコマンドを引数として渡すことで、fiebase-toolsを使えるようになります。
ただし、Firebase CLIをCIツールで使うためには認証に必要なトークンを取得し、`jobs.deploy.steps.env`で`FIREBASE_TOKEN`にわたす必要があります。

#### Firebase CLIの認証トークンを設定
まずはトークンを取得します。
ローカルのプロジェクトルートで`firebase login:ci`を実行すると、ブラウザが起動してログインを促されるのでGoogleアカウントでログインします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408311/fff3eadf-39b9-b612-224b-350d05558695.png" width=400>
ログインが成功したらシェルにトークンが表示されます。

```
Waiting for authentication...

✔  Success! Use this token to login on a CI server:

***************************************** // ここにトークン

Example: firebase deploy --token "$FIREBASE_TOKEN"
```

これでトークンの用意ができました。これをGitHubのリポジトリに設定できる環境変数に設定します。

#### 暗号化された環境変数を使う
GitHubはリポジトリに対して暗号化された環境変数を設定することができます。
リポジトリの**Settings** > サイドバーの**Secrets** > **New secret** > **Name**と**Value**を入力 > **Add secret**
という感じで非常に簡単かつ安全にトークンを保存できます。
そしてここで設定した環境変数を`secrets.FIREBASE_TOKEN`という形で呼び出すことができます。

## 実行してみる
これでCI/CD環境は構築できたので、早速このワークフローを試してみましょう。適当にコミットして`master`ブランチにプッシュします。
するとリポジトリの**Actions**タブから、実行されたワークフローのログを確認できます。
![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408311/d754dce6-c434-7282-294b-7cc876046d11.png)
見事全てのジョブが完了しました:grin:

## 参考
[GitHub Actionsのワークフロー構文](https://help.github.com/ja/actions/reference/workflow-syntax-for-github-actions#about-yaml-syntax-for-workflows)
[成果物を使用してワークフローデータを永続化する](https://help.github.com/ja/actions/configuring-and-managing-workflows/persisting-workflow-data-using-artifacts)
[GitHub Action for Firebase](https://github.com/marketplace/actions/github-action-for-firebase)
[Firebase CLI リファレンス #CI システムで CLI を使用する](https://firebase.google.com/docs/cli?hl=ja#cli-ci-systems)
[暗号化されたシークレットの作成と保存](https://help.github.com/ja/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets)