Travis CI で Firebase の CI/CD 環境を構築する

こんにちは。

タイトルまんまです。Continuous Integration/Continuous Deploymentです。継続的インテグレーション/継続的デプロイです。

丁寧に書こうとしすぎて長くなっていますが、この話の**本質は[ここ](#travisyml-を書く)**にあります。
お急ぎの方はどうぞ。

# 前提

バージョン6以上のnodeをインストール済み

# Firebase

兎にも角にもCI/CDを回す対象となるFirebaseプロジェクトが必要です。
さくっとFirebaseのプロジェクトを用意しましょう。

※すでにFirebaseのプロジェクトがある場合は[Travis CI](#travis-ci)の節までスキップしてください。

## Firebase consoleでプロジェクトを作成する

[Firebase console](https://console.firebase.google.com/?hl=ja)にログインし、プロジェクトを作成しましょう。

本当はfirebase-toolsでもプロジェクトを作成できますが、どっちにしろデフォルトロケーションの設定をするためにFirebase consoleに入る必要があるのでこの方が早いと思います。

「プロジェクトを追加」をクリックします。

<img width="1439" alt="スクリーンショット 2019-08-30 0.15.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75063/8a319862-46b5-f68e-690d-704a9e2ebd8a.png">

プロジェクト名を入力します。

<img width="1431" alt="スクリーンショット 2019-08-30 0.16.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75063/7db11309-6573-91e8-7080-ba63bf664a38.png">

Google アナリティクスを利用するか聞かれます。
今回は不要なので「今は設定しない」を選択します。

<img width="1436" alt="スクリーンショット 2019-08-30 0.17.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75063/d8771595-3ae4-9480-024b-03090f7523ae.png">

少し待つとプロジェクトが作成されます。

これで完了と言いたいのですが、次のステップで`firebase init`を実行するときに、デフォルトロケーションを設定していないと怒られるので、デフォルトロケーションの設定も今のうちに終わらせておきます。

プロジェクトの詳細画面に移動し「プロジェクトの設定」をクリック。

<img width="1434" alt="スクリーンショット 2019-08-30 0.18.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75063/800516ef-93ee-273c-84a3-277566be62de.png">

「Google Cloud Platform（GCP）リソース ロケーション」という項目が未設定となっていると思うので、右の編集ボタンをクリックしてロケーションを選択します。

今回は「asia-northeast1」を選択します。
asia-northeast1は東京、asia-northeast2は大阪らしいです。

他のロケーションについては[Cloud Firestore のロケーション](https://firebase.google.com/docs/firestore/locations?hl=ja)を参照してください。

<img width="1438" alt="スクリーンショット 2019-08-30 0.18.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75063/7aec0c7f-cc8f-8f20-3d6c-9a12728ffdd7.png">

Firestoreを使用する場合、予めデータベースを作成する必要があります。
「Database」に移動します。

<img width="1437" alt="スクリーンショット 2019-08-30 2.27.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75063/a70715c4-ad45-a4df-d014-809150c96a1e.png">

お好きな方を選択して次へ。

<img width="906" alt="スクリーンショット 2019-08-30 2.27.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75063/34b092a1-5390-ff1a-9a71-672ee00d5b36.png">

Databaseを作成する場合は、ここでロケーションを設定しても良いです。

<img width="756" alt="スクリーンショット 2019-08-30 2.27.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75063/c0b343f9-6359-deb3-a4dd-fd3798a43afa.png">

Firebaseプロジェクトの作成は以上です。

## firebase-toolsをインストール

[公式ドキュメント](https://firebase.google.com/docs/cli?hl=ja)を参考に、firebase-toolsをインストールしましょう。

firebase-toolsとは、プロジェクトのボイラープレートを生成してくれたり、デプロイしたりするのにも使えますし、今回はあとでTravis CIに設定するためのトークンを取得するのにも使います。

```bash:bash
npm install -g firebase-tools
```

インストールが完了したら以下のコマンドを実行してGoogleアカウントでFirebaseにログインします。

```bash:bash
firebase login
```

## firebase-toolsでプロジェクトを用意する

Firebaseプロジェクトを作成したいフォルダで、以下のコマンドを実行します。

```bash:bash
firebase init
```

すると使用したいFirebaseの機能を聞かれるので、必要なものを選びます。
今回はDatabase以外のすべてを選択します。
選択したらEnterへ次へ。

```bash:bash
? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices. 
 ◯ Database: Deploy Firebase Realtime Database Rules
 ◉ Firestore: Deploy rules and create indexes for Firestore
 ◉ Functions: Configure and deploy Cloud Functions
 ◉ Hosting: Configure and deploy Firebase Hosting sites
❯◉ Storage: Deploy Cloud Storage security rules
```

続いてFirebaseプロジェクトについて聞かれます。
今回は作成済みなので `Use an existing project` を選びます。

```bash:bash
? Please select an option: (Use arrow keys)
❯ Use an existing project 
  Create a new project 
  Add Firebase to an existing Google Cloud Platform project 
  Don't set up a default project 
```

プロジェクトを選びます。

```bash:bash
? Select a default Firebase project for this directory: (Use arrow keys)
❯ fir-travis-ci-example-f33dd (firebase-travis-ci-example) 
```

質問に答えていきます。
今回はFunctionsの設定だけ変更していますが、それ以外はデフォルトのままです。

```bash
=== Firestore Setup

Firestore Security Rules allow you to define how and when to allow
requests. You can keep these rules in your project directory
and publish them with firebase deploy.

? What file should be used for Firestore Rules? firestore.rules

Firestore indexes allow you to perform complex queries while
maintaining performance that scales with the size of the result
set. You can keep index definitions in your project directory
and publish them with firebase deploy.

? What file should be used for Firestore indexes? firestore.indexes.json

=== Functions Setup

A functions directory will be created in your project with a Node.js
package pre-configured. Functions can be deployed with firebase deploy.

? What language would you like to use to write Cloud Functions? TypeScript
? Do you want to use TSLint to catch probable bugs and enforce style? Yes
✔  Wrote functions/package.json
✔  Wrote functions/tslint.json
✔  Wrote functions/tsconfig.json
✔  Wrote functions/src/index.ts
✔  Wrote functions/.gitignore
? Do you want to install dependencies with npm now? Yes

=== Hosting Setup

Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to be uploaded with firebase deploy. If you
have a build process for your assets, use your build's output directory.

? What do you want to use as your public directory? public
? Configure as a single-page app (rewrite all urls to /index.html)? No
✔  Wrote public/404.html
✔  Wrote public/index.html

=== Storage Setup

Firebase Storage Security Rules allow you to define how and when to allow
uploads and downloads. You can keep these rules in your project directory
and publish them with firebase deploy.

? What file should be used for Storage Rules? storage.rules

i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...
i  Writing gitignore file to .gitignore...

✔  Firebase initialization complete!
```

このあとビルドで何も書いてないとエラーとなってしまうため、`functions/index.ts`内のソースをコメントアウトしておきます。

```typescript:functions/index.ts
import * as functions from 'firebase-functions';

// // Start writing Firebase Functions
// // https://firebase.google.com/docs/functions/typescript
//
export const helloWorld = functions.https.onRequest((request, response) => {
 response.send("Hello from Firebase!");
});
```

またlintとbuildは後述の`.travis.yml`に書くため、`firebase.json`の`predeploy`は消してしまいましょう。

```json:firebase.json
{
  "firestore": {
    "rules": "firestore.rules",
    "indexes": "firestore.indexes.json"
  },
  "functions": {
  },
  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ]
  },
  "storage": {
    "rules": "storage.rules"
  }
}
```

以上でプロジェクトのセットアップは完了です。

このプロジェクトをGitHubの適当なリポジトリにPushしておいてください。

この時点では [https://github.com/itigoore01/firebase-travis-ci-example/tree/276976b66b0cb025d9e441447473415b28f952d9](https://github.com/itigoore01/firebase-travis-ci-example/tree/276976b66b0cb025d9e441447473415b28f952d9) のようなファイル構成になっているはずです。

これでCI/CDを回す準備が整いました。
これからが本番です。


# Travis CI

Travis CIの設定をしていきます。

## リポジトリのCIを有効化する

Travis CIにログインし、右上のアバター画像をクリックして設定画面を開きます。

設定画面にGitHubに作成したリポジトリの一覧が表示されているので、対象のリポジトリをONにします。

<img width="1284" alt="スクリーンショット 2019-08-30 0.51.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75063/2df6069f-b65e-b5ac-0646-6187c45697f7.png">

## .travis.yml を書く

書いていきます。

```yml:.travis.yml
language: node_js
node_js:
  - "10"
script:
  - npm --prefix "./functions" install
  - npm --prefix "./functions" run lint
  - npm --prefix "./functions" run build
deploy:
  provider: firebase
  skip_cleanup: true
  token:
    secure: $FIREBASE_TOKEN
```

以上です。

たまに `firebase-tools` をインストールして `firebase deploy --token $FIREBASE_TOKEN` を実行している例を見ますが、Travis CIにはFirebaseにデプロイするためのプロバイダーが用意されているので、本当はそれすら必要ありません。

ちなみに、`script`のところにある `npm --prefix "./functions" install`は`functions`内にあるpackage.jsonのパッケージをインストールするためのコマンド、そのあと同フォルダのlint、buildと続きます。

`Functions`を使っていない場合は代わりに `echo ""`などのコマンドに置き換えてください。
（置き換える必要あるのか？と思われるかもしれませんが、今回の例では`npm test`を勝手に実行されてエラーとなってしまうため置き換える必要があります。）

## Travis CIにFirebaseのTokenを設定する。

`.travis.yml`に`$FIREBASE_TOKEN`というものが登場しましたが、これは当然勝手に入るものではないので自分で設定してあげる必要があります。

まずシェルで以下のコマンドをたたき、Travis CIに設定するためのトークンを取得します。

```bash:bash
firebase login:ci
```

ブラウザが立ち上がるので、ログインするとシェルに以下のようなトークンが出力されているはずなので、これをクリップボードにコピーします。

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

1/detaRAmEnatokundesUcbfT2-EYYrkAFU5fiM-N6bdB
```

Travis CIの設定画面に移り、対象のリポジトリの「Settings」ボタンをクリック。

<img width="1118" alt="スクリーンショット 2019-08-30 1.33.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75063/5894d774-eaa0-c83d-dade-04052da567fc.png">

「Envrionment Variables」の項目を入力し、「ADD」ボタンで追加します。

NAME: FIREBASE_TOKEN （ドルマークは不要です）
VALUE: 先の手順で取得したトークン

<img width="1042" alt="スクリーンショット 2019-08-30 1.34.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75063/46867a68-96ca-e4ba-9481-363d19979a5f.png">

以上で設定完了です。

`git commit` & `git push`してちゃんとデプロイされているかチェックしてみましょう。

Buildが走り出すとこんな感じになります。

<img width="1023" alt="スクリーンショット 2019-08-30 1.50.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75063/b4fbce50-824d-6800-b591-0b1c63dac044.png">

成功するとこうなります。
ちなみにビルド番号が変わっているのは、記事を書く途中全然うまいこといかなかったからです。無念。。。

<img width="1069" alt="スクリーンショット 2019-08-30 2.37.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75063/243842ec-326f-2976-6c18-cdcf180ffbac.png">

## 確認

まずはGitHubとのCI連携を確認します。
適当にプルリクエストを作成してテストしてみます。
エラーが出るのがわかりきっている悪意溢れるプルリクエストを作成してみましょう。

![スクリーンショット 2019-08-30 10.19.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75063/c9d42a8a-0a8a-8dba-b913-5d5c5d3eb9c0.png)

ちゃんと反映されていますね。
Firebaseも見ていきます。

Functionsに関数が増えています。

<img width="1437" alt="スクリーンショット 2019-08-30 2.39.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75063/9ca80577-1a9a-6b6a-2597-61849ada80a7.png">

Hostingもちゃんと出来てますね。

<img width="1438" alt="スクリーンショット 2019-08-30 2.40.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75063/3153e948-4629-7be8-0362-ad4268858423.png">

# さいごに

今回の例のソースコードは以下にありますので参考まで。

[https://github.com/itigoore01/firebase-travis-ci-example](https://github.com/itigoore01/firebase-travis-ci-example)