CircleCI から Firebase にデプロイしたり、ファイルアップしたりする

GitHub で特定ブランチにマージした時などに Firebase Hosting へデプロイしたり、Cloud Storage にファイルをアップしたりする作業を CircleCI を使って、自動化する例です

## CircleCI から Firebase Hosting へデプロイする

1. master ブランチにマージ
1. `webpack --mode=production`でビルドする
1. `firebase deploy`コマンドで Hosting へデプロイする

```yaml
references:
  commands:
    setup-docker-base: &setup-docker-base
      docker:
        - image: kurosame/circleci-node

version: 2
jobs:
  build-deploy:
    <<: *setup-docker-base
    steps:
      - checkout
      - restore_cache:
          name: Download and cache dependencies
          keys:
            - v1-dependencies-{{ checksum "package.json" }}
            - v1-dependencies-
      - run:
          name: Install
          command: yarn install
      - save_cache:
          name: Cache dependencies
          paths:
            - node_modules
          key: v1-dependencies-{{ checksum "package.json" }}
      - run:
          name: Build
          command: yarn build:production
      - run:
          name: Deploy
          command: ./node_modules/.bin/firebase deploy --only hosting --project $FIREBASE_PROJECT --token $FIREBASE_TOKEN

workflows:
  version: 2
  build-deploy:
    jobs:
      - build-deploy:
          filters:
            branches:
              only:
                - master
```
Docker イメージは node が入っているイメージであれば何使っても良いと思います

CircleCI の環境変数の FIREBASE_TOKEN はその名の通り Firebase のトークンを設定してますが、トークンは`firebase login:ci`コマンドを実行することで取れます
`--only hosting`オプションを付けているのは、Hosting ターゲットのみのリソースをデプロイ対象にするためです
例えば Cloud Functions を使っていて、Functions のコードはデプロイしたくない場合などに使うと良いと思います

## CircleCI から Cloud Storage へファイルをアップロードする - `gsutil rsync`

1. master ブランチにマージ
1. Google Cloud SDK の gsutil コマンドを利用して、指定したディレクトリ内の全ファイルを Storage へアップロードする

```yaml
references:
  commands:
    setup-docker-gcloud: &setup-docker-gcloud
      docker:
        - image: google/cloud-sdk

version: 2
jobs:
  deploy-raw-data:
    <<: *setup-docker-gcloud
    steps:
      - checkout
      - run:
          name: Deploy to raw-data
          command: |
            echo $GCLOUD_SERVICE_KEY > /tmp/gcloud-service-key.json
            gcloud auth activate-service-account --key-file /tmp/gcloud-service-key.json
            gsutil -m rsync -d -r ./raw-data gs://glossary-kurosame.appspot.com/raw-data

workflows:
  version: 2
  build-deploy:
    jobs:
      - deploy-raw-data:
          filters:
            branches:
              only:
                - master
```

Google Cloud SDK がインストールされた Docker イメージ（google/cloud-sdk）を使うことで楽してます

CircleCI の環境変数の GCLOUD_SERVICE_KEY には、認証に使う鍵（JSON）を設定します
Firebase の「プロジェクトの設定 -> サービス アカウント -> 新しい秘密鍵の生成」で鍵を作り、JSON でダウンロードします
そして、そのダウンロードした JSON の中身をそのまま GCLOUD_SERVICE_KEY の値として貼り付けます

今回ローカルのディレクトリと Storage のディレクトリの中身を完全に同期させたいので`gsutil rsync -d`を使っています
単純にファイルをアップロードするだけであれば、`gsutil cp`で良いと思います
各オプションは以下の意味を持ちます

| オプション | 意味                                     |
| ---------- | ---------------------------------------- |
| -m         | 同期を並列化する                         |
| -d         | 削除も行う（デフォルトは追加と更新のみ） |
| -r         | ディレクトリを再帰的に同期する           |

## CircleCI から Cloud Storage へファイルをアップロードする - `gsutil cp`

ディレクトリ内の全ファイルコピーは、基本的に`gsutil rsync`で問題無いと思うのですが、変更があったファイルのみコピーしたいケースもあると思います
その場合は、`gsutil cp`で良いかなと思います

```yaml
references:
  commands:
    setup-docker-gcloud: &setup-docker-gcloud
      docker:
        - image: google/cloud-sdk

version: 2
jobs:
  deploy-raw-data:
    <<: *setup-docker-gcloud
    steps:
      - checkout
      - run:
          name: Deploy to raw-data
          command: |
            echo $GCLOUD_SERVICE_KEY > /tmp/gcloud-service-key.json
            gcloud auth activate-service-account --key-file /tmp/gcloud-service-key.json
            ######################################## rsyncをcpに変更 ########################################
            UP_FILES=変更したファイルを検知する処理
            for FILE in "$UP_FILES"
            do
              gsutil cp "./raw-data/${FILE}" gs://glossary-kurosame.appspot.com/raw-data
            done
            ######################################## rsyncをcpに変更 ########################################

workflows:
  version: 2
  build-deploy:
    jobs:
      - deploy-raw-data:
          filters:
            branches:
              only:
                - master
```

単純に変更があったファイルを検知して、for で回して`gsutil cp`してるだけです

上記コードの「変更したファイルを検知する処理」ですが、色々やり方はあると思いますが、例えば、以下のようにすると一応変更があったファイルを絞り込むことができます
`$(git diff HEAD^ | grep ファイル名 | sed -e 's/ファイル名以外の不要な部分//' | uniq -u)`

1. `git diff HEAD^`で直前のコミットとの差分を取得（今回のコミットで変更したファイルを取得するため）
1. ファイル名とかで grep する
1. sed でファイル名以外の不要な文字列を空文字で置換する
1. uniq で重複を排除