CircleCIお触り！Androidのビルド実行~サーバへのapkアップロードまでやってみた

#1.経緯
今回、CircleCIって便利そうだけどどうなのっていうのが社内で意見があり、これから新しくサービスやるかもっということだったので、触ってみました。
サーバ側で導入したいとのことですが、私、AndroidがメインなのでまずはAndroidをビルドして、サーバにapkアップロードしたいというところまで。
また、アプリ側的にはあくまでも社内のテスト時の配布用とかなので、今の所は利用する予定はないのですが、サーバ的に本番へのマージ作業とかの際、Push時にそのまま処理走るのが困るので、一度処理を止めて確認した上で後続の処理が走らせれるようにするところまでをやってます。iOS側もやる予定なので出来たらその時に記事書くかもです。
テスト実施してから、ビルドを走らせるとかはゆくゆくできるようにしたいなと思います。


#2.やったこと
【Androidのビルド,Apkの吐き出し】->【CircleCIの管理画面での承認待ち】-> 【社内のサーバにapkをアップロード】　
*正確には最後のアップロードまでは出来ていません。。。詳しくは3.導入の流れを参照


#3.導入の流れ
今回はテストで個人アカウントで試しました。CircleCIとGitHubの連携に関しては実際やる際は会社で取得したアカウントを利用予定です。

### CircleCIとGitHubを連携する
CircleCIの登録時にGitHubで登録すれば、作成時にすでに連携された形になります。
CircleCIにログインすると、下記のような画面が表示されます。何回がBuild実行を試しているので実行結果が出てますが、最初は何も表示されません。
![スクリーンショット 2019-11-26 19.25.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325007/d4b6296d-fb48-cae4-c0b9-6b5d9aea12e8.png)

CircleCIで実行する対象のプロジェクト(Repository)を設定します。
左側メニューの「ADD PROJECTS」-> 対象のRepositoryの「Set UP Project」を選択

![スクリーンショット 2019-11-26 19.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325007/f0b8deba-f8ce-e677-7359-5764d3bce6e2.png)

ymlファイル作ってプロジェクトにPushしてねっていうメッセージが出るので、最初はとりあえず、下記画面の「Copy to Clipboard」でSampleをコピーします。

![スクリーンショット 2019-11-26 19.34.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325007/18f630d4-a669-fdcb-0a6c-af30318448a7.png)

コピーしたら、自分の対象のプロジェクトのルートに.circleci/config.ymlの形でファイルを置きます。
今回の場合だと「VocaBook/.circleci/config.yml」の形になります。
追加したら、それをリモートのRepositoryにPushします。
Pushしたら、先ほどのページの「Start building」を押して、動作するかどうか確認します。

これで、一旦はCircleCIと対象のプロジェクトが連携された状態になったはずです。

### CircelCIの設定ファイルを書く
今の状態だと、CircleCI上でRepositoryのソースをcheckoutしてメッセージを表示するだけの状態なので、これからandroidのbuildが出来るように設定ファイルを修正していきます。

今回試しに作ったファイルがこちら。
*一部分かりやすいように変更しているため、コピペして利用してみようとしている場合は注意です！


```yml:

version: 2 

jobs: 
   build: 
     working_directory: ~/code 
     docker: 
       - image: circleci/android:api-28 
     environment: 
       JVM_OPTS: -Xmx3200m 
     steps: 
        - checkout 
        - run: 
           name: 依存関係のアップロード
           command: ./gradlew androidDependencies 
        - run: 
           name: アプリの作成
           command: ./gradlew assembleDebug
        - persist_to_workspace:
          root: .
          paths:
             - app/build/outputs/apk/debug/
   upload: 
     machine: 
       enabled: true 
     steps: 
       - add_ssh_keys: 
           fingerprints: 
           - "92:af:7e:2f:b6:f3:0d:4c:7c:a1:be:85:d5:4c:80:0a" 
       - run: 
           name: rsyncのダウンロード 
           command: 
             sudo -E apt-get update 
             sudo -E apt-get install -y rsync 
       - attach_workspace:
          at: .
       - run: 
           name: サーバへデプロイ 
           command: rsync -e "ssh -o StrictHostKeyChecking=no" --progress -avz app/build/outputs/apk/debug/app-debug.apk [アップロード先のURL] 
     
workflows: 
   version: 2 
   build_and_deploy: 
     jobs: 
       - build 
       - upload_approval: 
           type: approval 
           requires:  
             - build 
       - upload: 
           requires: 
             - upload_approval 
```

#### 設定ファイルを大雑把に確認する
ちょっと長いので、少しつづ分割して簡単に説明します。
まずは大枠で分けるとこんな感じです。

【jobs】
　->build
　->upload

【workflows】
　->build
　->upload_approval
　->upload

jobsの中に複数のjobを記載することが可能です。複数記載する場合はworkflowsを使ってjobの処理を管理する必要あります。workflowsがない場合はjobsの中の最初のjobしか実行されません。今回の場合だとbuildがそれにあたります。
今回はworkflowsで利用できるapprovalの機能を使うためにjobを分けていますが、必要ない場合は1つのjobにして、workflowsも記載不要です。

#### jobの「build」部分の詳細
まずは最初のjobである「build」部分から

```yml
build: 
     working_directory: ~/code 
     docker: 
       - image: circleci/android:api-28 
     environment: 
       JVM_OPTS: -Xmx3200m 
     steps: 
        - checkout 
        - run: 
           name: 依存関係のアップロード
           command: ./gradlew androidDependencies 
        - run: 
           name: アプリの作成
           command: ./gradlew assembleDebug 
        - persist_to_workspace:
           root: .
           paths:
             - app/build/outputs/apk/debug/
```

jobの名前は任意で設定できます。このjobではdockerにAndroidの環境を構築して、buildまでを行なってます。
[docker: - image:]のところで今回の作成したAndroidプロジェクトのターゲットビルドのバージョンを指定します。
記載方法は【android:api-xx】です。【api-xx-alpha】も指定出来るのですが、今回やった時には環境作成時にpermissionエラーが発生し、動作しなかったので、-alphaはつけないで実行しました。
[environment:]で環境変数の設定が可能です。
[steps:]にコマンド実行とか記載します。
[- checkout] ->これで対象のRepositoryからpullしてきます。
[- run:] -> 自分でコマンド実行したいものを記載できます。AndroidのGradlewを使って、ライブラリのダウンロードとビルド実行してます。
- persist_to_workspace -> jobが複数ある場合にファイルを共有するための設定です。今回、テスト的にjobを分けたので、apkファイルのパスを共有できるようにしました。


#### jobの「upload」部分の詳細
次に「upload」部分です。

```yaml
 upload: 
     machine: 
       enabled: true 
     steps: 
       - add_ssh_keys: 
           fingerprints: 
           - "92:af:7e:2f:b6:f3:0d:4c:7c:a1:be:85:d5:4c:80:0a" 
       - run: 
           name: rsyncのダウンロード 
           command: 
             sudo -E apt-get update 
             sudo -E apt-get install -y rsync 
　　　　- attach_workspace:
          at: .
       - run: 
           name: サーバへデプロイ 
           command: rsync -e "ssh -o StrictHostKeyChecking=no" --progress -avz app/build/outputs/apk/debug/app-debug.apk [アップロード先のURL] 
```

「machine:] でやっているのはこのタイミングではコマンド実行しか行わず、特殊な環境は必要ないためです。
[- add_ssh_keys: fingerprints:] ここで今回はサーバへのアップロードは公開鍵を使ってアップロードする必要があるので、最初に公開鍵を設定します。
こちらのfingerprintはCircleCIでSSHKeyの登録をすると提供されます。

SSH鍵の登録方法も記載しておきます。
#### SSH鍵の登録方法
Jobs->対象プロジェクトのSettingsへ
![スクリーンショット 2019-12-05 11.46.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325007/ca9f8521-a04b-0234-3b79-9bba33e7f154.png)

SettingsのSSH Permissionsを選択し、Add SSH Keyを選択
![スクリーンショット 2019-12-05 11.46.011.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325007/343847b2-1846-51cc-884e-494f52c335ee.png)

ダイアログでHostnameとPrivate Keyが出てくるので情報を入れてAdd SSH Keyのボタンを押すと、 FingerPrintsが表示されます。
![スクリーンショット 2019-12-05 11.52.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325007/bb7e0d97-b16b-0c0f-a424-52cb67501b6c.png)
Hostnameは設定しておくと、対象のHostnameだったら勝手に使ってくれるらしい? 空の場合も全体適応されるらしいけども、複数あったらどうなるなどは調べていないのでわからないです。
複数設定が必要そうな場合は基本的にconfigファイルに使う時に追記しておけば安全なのかなと思います。

これで、sshの設定まで終わりました。

あとは今回、rsyncコマンドでサーバへのアップロードを行うので、まずはrsyncをインストールしておきます。
[- attach_workspace:] ここでbuildのjobの際に設定したworkspaceの情報を共有できるように設定しています。
そのあとに、rsyncで対象サーバへアップロードして完了です。


#### workflows部分の詳細 
最後にWorkflowsの部分です。

```yaml
workflows: 
   version: 2 
   build_and_deploy: 
     jobs: 
       - build 
       - upload_approval: 
           type: approval 
           requires:  
             - build 
       - upload: 
           requires: 
             - upload_approval 
```

jobsの中に記載した内容が実行されます。処理を待つ必要がある場合、requiresのパラメーターをつけて対象のjobを待つようにします。
今回の例でいうと「upload_approval」のところでrequires:buildとありますが、buildが終わるとupload_approvalが実行されますが、uploadはupload_approvalが終わるまでは実行されません。
また、type:approvalを設定すると管理画面上で許可するまでは対象のjobは動きません。
upload_approvalは最初に紹介した「build」,「upload」でもないjobになります。こちらは待ち処理のためだけに設定したjobです。
最初は「build」と「upload」の2つのjobをworkflowsに設定していて、「upload」でapprovalを設定していたのですが、その場合だとapproveのボタンを押してもその対象のjobは動かずにそこで処理が終了するというトラップにはまりました。。。
認証用は空のjobを設定することで回避できるみたいなので、皆さんもworkflowsを設定する場合はこの辺りにお気をつけて。。。

管理画面の状態はこんな感じです。
![スクリーンショット 2019-12-05 13.18.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325007/048fccf1-0e34-9e14-7fe4-39b33b22ad90.png)

ステータスがON HOLDになって処理が止まっていることが分かります。
対象のjobをクリックすると下記画面が表示され、Approveをクリックすると次の処理が実行されます。
![スクリーンショット 2019-12-05 13.19.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325007/f937b03e-fa99-2dcc-c2c4-cd8eff2c6771.png)

#4.課題
###①IPアドレス制限が入っているサーバに対してアップロードできない。。。
これで実行してもなぜがrsyncの時にタイムアウトする。。。なぜだ。。。と考えていたのですが、ローカルで動かすといけるという状態に。。。
これは会社の環境とか怪しいぞと思い、インフラチームに聞いたところ、対象のサーバにIP制限かかってるとのこと。。。
じゃあ、どうするかなーと思い調べていたところ、公式の回答ありました。
こちら。
https://support.circleci.com/hc/ja/articles/115014372807-IP-アドレスのホワイトリスト登録-

CircleCI側に固定のIPアドレスを振るのは無料版だと難しい。。。という結果でした。
じゃあ、どうしましょう。というところで現在、インフラ側の方と相談中。

###② ①の影響からworkspace間のコピーが正しくできているかがわからない
コマンドは成功しているので、きっと大丈夫なはず。。動かなかったらすみません。。
rsyncが動いてないので実際にファイルが上がるのかまでが未確認です。

### ③androidの作成したkeystoreでの署名
今回は手っ取り早く、Android Studioの備え付けのkeystoreで署名してますが、実際の運用となると作成したkeystoreを何かしらの方法でCircleCIに上げておく必要があります。
CircleCIにkeystoreファイルを上げるようなところはなさそうなので、上げれる形に変換するか、gitにあげておくか。
gitにあげておくと、場合によっては取られた時どうする問題があるので、プライベートの環境であればそれでもいいかなと思ってます。
こちらもまだ検討中です。


#5.最後に
yml形式で初めて書いたのですが、インテントがずれていてエラーになったりとなかなか慣れない書き方で苦労しました。
ちょっと長くなってしまったので記載しなかったのですが、毎回PushしてテストするとGitのコメントがカオスになるので、ある程度はローカル環境で動かしてテストしてます。
dockerとCircleCIの実行環境をインストールできるので、まずはそちらでコードが通るかどうかチェックしておくのがオススメです。
社内で出来る準備が整ったら情報更新しようかと思います。


#参考にしたページ
CircleCIをローカルで動かす
https://qiita.com/selmertsx/items/45bd672c2c8ddab1981b
https://qiita.com/kurkuru/items/127fa99ef5b2f0288b81

Workflowの設定とか
https://qiita.com/sawadashota/items/ba89382d563bc90bb5cd

rsyncのSSHでの接続時、エラーになってしまう時の解決
https://akamist.com/blog/archives/3195

sudo rsyncしたらエラーになってしまう時の解決
https://qiita.com/kazuhei/items/c7895a097c9d3da37a5a