Google Cloud Storageを用いたAndroidライブラリ用の社内Mavenリポジトリ構築

こんにちは。株式会社ビデオマーケットの仙台オフィスでMIRAILのiOS/Androidアプリ開発を担当しているasmzです。

はじめに

弊社では私が担当しているMIRAILの他にもいくつかアプリを開発しており、それらのアプリで共通的に利用されるような処理はライブラリ化しています。

ただ、これまで社内Androidライブラリをアプリ側に導入するまでの「仕組み化」が行われておらず、出力されたjarファイルを手作業でアプリ側プロジェクトの規定ディレクトリに配置する、という原始的な方法を取っていました。

当然ながらこれだと手作業による作業ミスが懸念されるほか、ライブラリのバージョンを変えながらの動作確認などもしづらく、管理面・開発面それぞれで不都合を感じていました。

そこで今回、社内向けのプライベートなMavenリポジトリを立てて、自動でAndroidライブラリ成果物の管理とアプリ側への導入を行える仕組みを構築してみましたので、その対応概要と手順をご紹介します。

対応概要

今回の対応の全体構成は以下のような形になります。

f:id:asmz0:20210207153256p:plain

やることしては大きく2つに分けられます。

  1. Androidライブラリにリリースタグを付与したら、自動でビルドし成果物を社内Mavenリポジトリへアップする
  2. Androidアプリのプロジェクトから社内Mavenリポジトリを参照し、社内ライブラリを取得する

最終的には以下のように、他のライブラリと同じような形でbuild.gradleに記述することで社内ライブラリを導入できることを目指しました。

dependencies {
    :
    // In-house library
    implementation 'your.maven.repo:inhouse-library:x.x.x'
    :
}

事前準備:Google Cloud Storageの準備

社内Mavenリポジトリのストレージとして、今回はGoogle Cloud Storage(以下、GCS)を使います。

選定理由としては主に以下です。

  • Firebaseと統合されたGCPプロジェクトなら、Firebase側とアカウントや権限が共有できる
  • Gradleが公式にGCS Maven Repositoryをサポートしている

PrivateなMavenリポジトリとなるため何らかの認証機構が必要ですが、元々アプリ開発者は既にFirebaseを利用しているので、今まで通りFirebaseのアカウント管理のみで、結果的にGCSへのアクセス制御も出来るということになります。(Firebase統合されていないGCPプロジェクトではこの形は使えないので、別な方法を検討する必要があります)

社内Mavenリポジトリ用Bucket作成

まずはMavenリポジトリの実体となるBucketをGCSに作成します。

この辺りの作業はGCSの一般的な手順と同じなので細かい手順の説明は省略します。詳細な手順については公式のドキュメントをご参照ください。

cloud.google.com

Bucket名
  • グローバルに一意となる任意の名前でOK
リージョン設定
  • そんなに可用性を気にするほどでもないので「単一リージョン」で良いかなと
  • ロケーションはとりあえず「asia-northeast1(東京)」
ストレージクラス、アクセス制御、詳細設定
  • この辺りは利用環境やアクセス頻度、利用料金など、各プロジェクトの状況に応じた設定を選んで貰えば良いかなと思いますが、ひとまずデフォルトのままでも動作します

最後に「作成」ボタンを押下すれば、Bucketの作成は完了です。

サービスアカウント用意

先述した通り、Firebaseと統合されたGCPプロジェクトであれば各自のFirebaseアカウントでGCSへアクセスできますが、CI環境からGCSへのアクセスにはサービスアカウントを利用します。こちらもGoogle Cloud Console上で作業します。

cloud.google.com

サービスアカウントの権限設定

今回のこのサービスアカウントは社内Mavenリポジトリの操作(専用バケット内へのObjectの作成・閲覧)にしか使用しないため、以下のような形に設定しました。

  • ロール:Storageオブジェクト管理者
  • 条件:CEL式でyour-maven-repoパス配下のObjectのみ対象とするよう指定
resource.type == "storage.googleapis.com/Object" &&
resource.name.startsWith("projects/_/buckets/your-maven-repo/")

この辺りはプロジェクトそれぞれで運用しているセキュリティポリシーに合わせた設定を行うのが良いかと思います。

サービスアカウントキー作成

こちらも公式ドキュメントに沿って作成します。

cloud.google.com

作成後にダウンロードできる秘密鍵はキータイプ「JSON」を選択し、どこかに保管しておいてください。後ほどCI環境整備で必要となります。

f:id:asmz0:20210201224519p:plain:w400

AndroidライブラリをGCSへアップロード

ここからが本題ですが、実際にAndroidライブラリをGCSへアップしていきます。最初の構成図で言うとこの部分に当たります。

f:id:asmz0:20210207162647p:plain:w600

Mavenへ公開するGradleタスク用意

AndroidライブラリのMavenへの公開については、以下の通りAndroidの公式ドキュメントで「Maven Publish プラグイン」を使う方法が紹介されているので、これを利用します。

developer.android.com

ドキュメント内に記載がありますが、このタスクはafterEvaluate内で行う必要があります。

また、今回アップ対象となるGCSの指定も必要ですが、以下の通りGradleは公式にGCSのMavenリポジトリをサポートしています。

docs.gradle.org

GCSをMavenリポジトリとして使用する場合は、gcs://<bucketName>/<objectKey>というURLスキーマで指定すれば良いようです。
今回はGCSのBucketにmavenというフォルダを掘って、その配下をMavenリポジトリとして扱うことにしました。

以上を踏まえ、Androidライブラリのbuild.gradleに以下のような内容を追記します。

// Upload to In-house Maven
afterEvaluate {
    publishing {
        publications {
            release(MavenPublication) {
                groupId "your.maven.repo"
                artifactId = 'inhouse-library'
                version "x.x.x"
                from components.release
            }
        }
        repositories {
            maven {
                url "gcs://your-maven-repo/maven"
            }
        }
    }
}

これでMaven Publish プラグインによって./gradlew publish というMavenへ成果物をアップロード・公開するタスクが用意されます。

BitriseにGCS認証設定

前述のGradleタスクをCI上で実行することによって自動化を図りますが、PrivateなGCS環境へアクセスするための認証設定が必要となりますので、ここで先に用意しておいたサービスアカウントを使います。

cloud.google.com

上記ドキュメントの通り、環境変数GOOGLE_APPLICATION_CREDENTIALSに、先ほど保管しておいたキーJSONファイルの配置パスを設定することで、アプリケーションのデフォルト認証情報(ADC)がそれを読みに行って認証してくれます。

CI環境上にファイルを配置するやり方はいろいろありますが、今回はCIサービスとしてBitriseを使用しますので、Bitriseの「GENERIC FILE STORAGE」へJSONファイルをアップしておき、それをFile DownloaderステップでCI環境へダウンロードする、という形で配置します。

Bitriseの「GENERIC FILE STORAGE」

f:id:asmz0:20210208095423p:plain:w500

File Downloaderステップ
  • Download destination pathに任意の配置パスを指定

f:id:asmz0:20210207174122p:plain:w300

f:id:asmz0:20210208095444p:plain:w500

環境変数へセット

これでビルド時にCI環境上にJSONファイルが配置されるので、そのパスを環境変数GOOGLE_APPLICATION_CREDENTIALSに設定すればBitrise→GCSの認証設定はOKです。

envman add --key=GOOGLE_APPLICATION_CREDENTIALS --value="${BITRISE_SOURCE_DIR}/gcs-maven-admin-credentials.json"
ライブラリのビルド、GCSへのアップ

あとはBitbucketからBitriseへのHookを設定し、Tag PushをTriggerとしたWorkflowで以下のGradleタスクを実行すれば、自動的にビルドし成果物がGCSへアップされます。

./gradlew publish

ちなみにこのタスク実行後にGCSの方を覗いてみると、このような感じでライブラリ本体の他に各種メタ情報やpomファイルなども自動で作られてアップされていることがわかります。

f:id:asmz0:20210207175804p:plain:w300

社内Mavenリポジトリからライブラリ取得

ライブラリ側の作業はここまでで完了し、最後にそのライブラリを利用するアプリ側の作業です。

f:id:asmz0:20210207180133p:plain:w600

ローカル環境にGCS認証設定

各開発者側の事前準備

先述の通りFirebaseにアカウント(「編集者」以上の権限)があればGCSへのアクセスが可能なので、もしまだFirebaseにアカウントがなければ先にアカウント登録を済ませておきます。

また作業上必要となるため、gcloudコマンドをローカルにインストールしておきます。

cloud.google.com

ローカル認証設定

以下のコマンドでGoogleアカウント認証を行います。

$ # GCPプロジェクト指定
$ gcloud config set project <your-gcp-project-id>

$ # ブラウザが立ち上がるので、Firebaseアカウント(Googleアカウント)でログイン
$ gcloud auth application-default login

完了するとそのアカウントがGCPプロジェクトのデフォルト認証情報(ADC)として保存され、以降のGCP認証で自動的に使用されます。

build.gradleでライブラリ取得

アプリレベルのbuild.gradleに以下を追記します。

repositories {
    maven { url 'gcs://dev-mirail-android-libs/maven' }
}

dependencies {
    :
    // In-house library
    implementation 'your.maven.repo:inhouse-library:x.x.x'
}

これでGradle Syncして、エラーなくライブラリが導入できたら完成です。

まとめ

以上で、今回の目標として掲げていた「他のライブラリと同じように社内ライブラリを導入する」という形にだいぶ近づけることができたかなと思っています。
他の有名なライブラリ達と並べて自分たちが作った社内ライブラリを書けるのは、思ったより気分が良いものですね!

まぁもちろんそれだけではなく、今まで手作業が介在していたことで抱えていた工数換算されないようなコストやリスクを、今回の自動化の仕組みによって廃すことが出来たのが大きな成果かなと思っています。

こういったあまり見えない工数も、積り積もると開発体験(DX)に影響してモチベーション低下に繋がったりしますので、このような形で極力自動化・仕組み化して、本業の開発に集中できる体制を作っていきたいですね。

参考

記事中に記載の公式ドキュメントの他に、以下の記事も参考にさせていただきました。有益な情報の公開頂きありがとうございます!