Androidアプリのbuild.gradleの情報の一部を暗号化管理した状態でCircleCIを使う


## はじめに

3月23日（木）に行われた[Gotanda.mobile #2](https://gotandamb.connpass.com/event/50876/)で発表した内容になります。

### LT資料：

- [AndroidアプリをOSSで運用してみる // Speaker Deck](https://speakerdeck.com/yamacraft/androidapuriwoossdeyun-yong-sitemiru)
- [yamacraft/android.RequestPermissions: RequestPermissions動作確認用サンプルプロジェクト](https://github.com/yamacraft/android.RequestPermissions)

## 概要

個人開発しているAndroidアプリをOSSとして運用する上で、「公に見せたくない情報の管理をどうするか」という懸念点があると思います。

今回はその中で`build.gradle`内の情報の一部をリポジトリ上で見えない形で運用する方法の一部を共有します。

あくまでもこれが正解なのかどうかわからないので、より良い方法があればコメントなどにご意見おねがいします！

## local.propertiesによるテキストの保存

Androidプロジェクトで「リポジトリに残したくないテキスト情報」はlocal.propertiesで管理するのが一般的です。

```txt:local.properties
## This file is automatically generated by Android Studio.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Mon Mar 20 10:49:41 JST 2017
sdk.dir=/XXXX/android-sdk

RELEASE_STORE_PASSWORD=XXXXXX
RELEASE_KEY_ALIAS=XXXXX
RELEASE_KEY_PASSWORD=XXXXX
```

```gradle:app/build.gradle
apply plugin: 'com.android.application'
android {

	// local.propertiesの読み込み
	Properties properties = new Properties()
    properties.load(project.rootProject.file('local.properties').newDataInputStream())

    signingConfigs {

        release {
            storeFile rootProject.file('release.jks')
            storePassword  properties.getProperty("RELEASE_STORE_PASSWORD")
            keyAlias properties.getProperty("RELEASE_KEY_ALIAS")
            keyPassword properties.getProperty("RELEASE_KEY_PASSWORD")
        }
    }
```

local.properties自体はAndroidプロジェクトの.gitignoreにデフォルトで記載されていますので、リポジトリに入ることはありません。

ただしこのとき、CI等がビルドしようとした際に`properties.load(project.rootProject.file('local.properties').newDataInputStream())`の処理でlocal.propertiesが見つけられずエラーを吐いてしまいます。

なので、CIではビルド前に空のlocal.propertiesファイルを作るとよいでしょう。

```yml:circle.yml
checkout:
  post:
    - cp local.properties.ci local.properties
```

これ、普通に`- echo > local.properties`でも良かったかなあと……


## Circle CIの環境変数を利用する

先ほどの設定の場合、自身のローカル環境以外、特にCircleCIではlocal.propertiesがない（もしくは空）ためビルドができません。

どうするかというと、CircleCIの環境変数を利用します。
該当のプロジェクトの設定から「Environment Variable」を選び、「Add Variable」で環境変数を設定します。

![Project_settings_-_yamacraft_android_RequestPermissions_-_CircleCI0.png](https://qiita-image-store.s3.amazonaws.com/0/4560/f41a1620-5b41-6d63-bc09-255b53d4ef29.png "Project_settings_-_yamacraft_android_RequestPermissions_-_CircleCI0.png")

こうして登録した環境変数のvalueは最後の数文字以外は「x」で埋められるため、入力した本人でも内容がわからないようになります。

![Project_settings_-_yamacraft_android_RequestPermissions_-_CircleCI.png](https://qiita-image-store.s3.amazonaws.com/0/4560/a1942c45-3bce-3ddd-4052-46e17ee47dda.png "Project_settings_-_yamacraft_android_RequestPermissions_-_CircleCI.png")


ただ問題があって、CircleCIの環境変数には上限があるらしく（しかもとても小さい）、今回のテストプロジェクトでは必要な変数を全て登録できませんでした。

[CircleCIに任意のデータを登録しておく方法 - Qiita](http://qiita.com/minodisk/items/ce488178d74ce63c3e53#%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0%E3%81%AEvalue%E3%81%AB%E7%99%BB%E9%8C%B2%E5%8F%AF%E8%83%BD%E3%81%AA%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%E3%81%AE%E4%B8%8A%E9%99%90%E3%81%AF)

ということでどうしたかというと、Circle CIのドキュメントにある環境変数の登録の手段として、「暗号化したテキストデータをリポジトリに含め、CIが動くときに復号化して`~/.circlerc`に追記する」があったのでそれを利用することにしました。

- [Environment Variables - CircleCI](https://circleci.com/docs/1.0/environment-variables/#keeping-encrypted-environment-variables-in-source-code)
- [circleci/encrypted-files: Storing encrypted files in source on CircleCI](https://github.com/circleci/encrypted-files)

ということで、

```txt:secret-env-plain
export RELEASE_STORE_PASSWORD=xxxxxxxxxx
export RELEASE_KEY_ALIAS=xxxxxxxxx
export RELEASE_KEY_PASSWORD=xxxxxxx
```

というファイルを `$openssl aes-256-cbc -e -in secret-env-plain -out secret-env-cipher -k $KEY` で暗号化した`secret-env-cipher`をリポジトリに加え、Circle CIの環境変数にKEYを登録し、

```yml:circle.yml
dependencies:
  pre:
    - openssl aes-256-cbc -d -in secret-env-cipher -k $KEY >> ~/.circlerc
```

とcircle.ymlに記述することで、復号化した中身を`~/.circlerc`に追記して環境変数の登録を対応しました。
当たり前ですが、`secret-env-plain`は.gitignoreでリポジトリの除外対象として対応します。

こうして取得した環境変数をbuild.gradleから読み込む場合は`System.getenv("環境変数名")`をつかいます。

```gradle:build.gradle
apply plugin: 'com.android.application'
android {

    signingConfigs {

        release {
            storeFile rootProject.file('release.jks')
            storePassword  System.getenv("RELEASE_STORE_PASSWORD")
            keyAlias System.getenv("RELEASE_KEY_ALIAS")
            keyPassword System.getenv("RELEASE_KEY_PASSWORD")
        }
    }
```

## 複合：local.propertiesとcircle ciの環境変数を取捨選択する

ということでローカル環境、CI環境それぞれで必要な情報を暗号化する手段を紹介しましたが、個人的にやりたいことは「基本はlocal.propertiesを参照、なければ環境変数から取得」です。

これは`Properties.getProperty()`で「取得できなければ第二引数に書かれたものを返す」処理で対応することができました。
なのでサンプルプロジェクトのbuild.gradleでは最終的にこうなっています。

```gradle:build.gradle
apply plugin: 'com.android.application'
android {

	// local.propertiesの読み込み
	Properties properties = new Properties()
    properties.load(project.rootProject.file('local.properties').newDataInputStream())

    signingConfigs {

        release {
            storeFile rootProject.file('release.jks')
            storePassword  properties.getProperty("RELEASE_STORE_PASSWORD", System.getenv("RELEASE_STORE_PASSWORD"))
            keyAlias properties.getProperty("RELEASE_KEY_ALIAS", System.getenv("RELEASE_KEY_ALIAS"))
            keyPassword properties.getProperty("RELEASE_KEY_PASSWORD", System.getenv("RELEASE_KEY_PASSWORD"))
        }
    }
```

## おわりに

現時点のサンプルプロジェクトではrelease.jksが生バイナリのままリポジトリに上がっているなど、まだ個人的にイマイチな感じがあります。このあたりも追々解決し、ご紹介できればと思います。