GitHub Actionsで最新の状態をテストフライトで配信する

8 min読了の目安(約7200字TECH技術記事

iOSアプリを開発するなかで、開発中のパッケージを確認するためにテストフライトという仕組みを使います。
これをGitHub Actionsを使って自動化してみました。

「あるブランチにcommitされたら、最新の状態のビルドをテストフライトで配布」をGitHub Actionsで組む手順を紹介します。

実装の流れ

  1. fastlane matchを使った証明書管理を準備する
  2. fastlaneにテストフライトのためのレーンを実装する
  3. GitHub Actionsの設定をする

なお、fastlaneはすでにセットアップ済みであることを想定して書きます。
まだfastlaneを使ってない場合はこちらの記事などを参考にセットアップしてみてください。

fastlane matchを使った証明書管理を準備する

fastlane matchはfastlaneアクションの一つで、iOSの煩雑な証明書管理をシンプルに行うためのものです。開発メンバー間で異なる証明書を使ったり、CI用の証明書に困ったりする問題を解決できます。(公式ドキュメントはこちら

まずはmatchの初期化を行い、

fastlane match init

証明書を作成します。

fastlane match appstore

今回はテストフライトのビルドで使う証明書が欲しいので、appstoreを指定します。

fastlane matchでは証明書管理のためのスペースを別で作成し、そこで証明書やプロビジョニングファイルを管理します。
コマンドの途中で管理方法やURLを聞かれるので入力しましょう。今回はGitのリポジトリを指定しました。

確認のためにfastlane/Matchfileの中身をみてみましょう。
指定した方法(git)とリポジトリのURLが記述されていることが分かります。

git_url("https://github.com/username/repository_name.git")
storage_mode("git")

さらに、コマンドの途中で MATCH_PASSWORD が尋ねられます。これは適当なものにしてOKですが、後でCIに設定するのでメモしておきましょう。

次に、以下のコマンドで作成した証明書を取得します。

fastlane match appstore --readonly

readonlyを指定することで証明書を取得できます。ひとりのオーナーが証明書を一回だけ作成し、残りのメンバーはその証明書を利用することで全員が共通の証明書を利用できます。

ダウンロードした証明書とプロビジョニングファイルの場所はfastlane matchの実行ログに表示されており、ファイルをダブルクリックしてimportできます。
Xcodeを開いて、証明書とプロビジョニングがセットできることを確認しておきましょう。また、Auto Signingはオフにしておくと良いです。

証明書の準備はここまでで完了です。
fastlaneコマンドに失敗する場合など、さらに詳しい情報が必要な場合は以下の記事が詳しく参考になります。

また、matchを使わない証明書管理をしたい場合は以下の記事が参考になると思います。

fastlaneにテストフライトのためのレーンを実装する

GitHub Actionsで実行されるfastlaneのレーンを用意します。ソースコードは以下のようになります。

lane :beta do
  # 1. Keychainの準備
  setup_ci(provider: "travis")

  # 2. AppStoreConenct APIの準備
  api_key = app_store_connect_api_key(
    key_id: ENV['ASC_KEY_ID'],
    issuer_id: ENV['ASC_ISSUER_ID'],
    key_content: ENV['ASC_KEY_CONTENT'],
    in_house: false
  )

  # 3. GitHubアクセスの準備
  username = ENV['USERNAME']
  personal_github_access_token = ENV["PERSONAL_GITHUB_ACCESS_TOKEN"]
  authorization_token_str = "#{username}:#{personal_github_access_token}"
  basic_authorization_token = Base64.strict_encode64(authorization_token_str)

  # 4. fastlane matchを使って証明書をインストール
  match(
    git_basic_authorization:basic_authorization_token,
    api_key: api_key,
    app_identifier: 'com.example.myapp',
    type: "appstore",
    readonly: is_ci
  )

  # ビルド番号をインクリメント
  increment_build_number(xcodeproj: "MyApp.xcodeproj")

  # アプリをビルド
  build_app(
    workspace: "MyApp.xcworkspace",
    scheme: "MyApp",
    export_options: {
      method: "app-store",
    }
  )

  # 5. テストフライトで配布
  upload_to_testflight(
    api_key: api_key,
    skip_waiting_for_build_processing: true
  )
end

少し補足の説明を書きます。

1. Keychainの準備

setup_ci(provider: "travis")

いきなりTravisを指定してるのが変な感じがしますが、こちらはfastlaneのIssueで議論されていた方法を参考にして記述しました。内部的にはキーチェーンの準備をしているようなので、別の方法でも良いかもしれません。
ちなみにこの一行がないと'[CP] Embed Pods Frameworks'で停止してビルドが進まなくなります。

2. AppStoreConenct APIの準備

api_key = app_store_connect_api_key(
  key_id: ENV['ASC_KEY_ID'],
  issuer_id: ENV['ASC_ISSUER_ID'],
  key_content: ENV['ASC_KEY_CONTENT'],
  in_house: false
)

かつて、fastlaneで申請をするときはCI上で二段階認証を越えるためにFASTLANE_SESSIONを毎回セットする必要があるなど手間がかかる部分が多くありました。
現在、fastlaneはAppStoreConnect APIをサポートしており、これを使うことでFASTLANE_SESSIONの更新などは必要なくシンプルに実現できます。詳しくは以下リンクをご参考ください。

API Keyを発行するために、以下の3つの情報が必要です。

  • ASC_KEY_ID
  • ASC_ISSUER_ID
  • ASC_KEY_CONTENT

これらを用意する方法はmogaさんの以下の記事がスクリーンショット付きで大変分かりやすいです。

AppStoreConenctから生成しましょう。後でCIのSecretsに設定するのでメモしておきましょう。

3. GitHubアクセスの準備

username = ENV['USERNAME']
personal_github_access_token = ENV["PERSONAL_GITHUB_ACCESS_TOKEN"]
authorization_token_str = "#{username}:#{personal_github_access_token}"
basic_authorization_token = Base64.strict_encode64(authorization_token_str)

fastlane matchでは別のリポジトリにアクセスして証明書をダウンロードする必要があります。ここでは別のリポジトリにアクセスするための認証情報を用意しています。

ここで、 USERNAME はGitHubアカウント名、 PERSONAL_GITHUB_ACCESS_TOKEN はGitHubで発行できるPERSONAL ACCESS TOKENです。(PERSONAL ACCESS TOKENの発行方法はこちらから

4. fastlane matchを使って証明書をインストール

match(
  git_basic_authorization: basic_authorization_token,
  api_key: api_key,
  app_identifier: 'com.example.myapp',
  type: "appstore",
  readonly: is_ci
)

fastlane matchを使って証明書とプロビジョニングファイルをダウンロードします。
CIから証明書の作成はしたくないので、readonly: is_ciを指定してリードオンリーで利用します。

5. テストフライトで配布

upload_to_testflight(
  api_key: api_key,
  skip_waiting_for_build_processing: true
)

ビルドしたアプリをテストフライトで配布します。FASTLANE_SESSIONがこれまで必要でしたが、api_keyを使った方法であれば2FAを越えられるため不要になっています。

GitHub Actionsでは利用時間に応じて料金がかかります。できるだけ時間を節約するためにskip_waiting_for_build_processing: trueをつけて、ビルドのアップロード完了を待たずに処理を終了しています。

ここまででfastlaneの準備は完了です。

GitHub Actionsの設定をする

それでは最後に、いま用意したfastlaneのレーンをGitHub Actionsから実行できるようにしましょう。
GitHub Actionsのワークフローは以下のように設定します。

name: Upload TestFlight

on: 
  workflow_dispatch:
  push:
    branches:
      - master

jobs:
  build:
    runs-on: macos-latest

    steps:
    - uses: actions/checkout@v2
      
    - name: Select Xcode version
      run: sudo xcode-select -s '/Applications/Xcode_12.3.app/Contents/Developer'
      
    - name: Show Xcode version
      run: xcodebuild -version
  
    - name: Bundle Install
      run: bundle install
       
    - uses: actions/cache@v2
      with:
        path: Pods
        key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
        restore-keys: |
          ${{ runner.os }}-pods-
          
    - name: Pod Install
      if: steps.cache-cocoapods.outputs.cache-hit != 'true'
      run: pod install
      
    - name: Upload a new build to App Store Connect
      env:
        ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
        ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
        ASC_KEY_CONTENT: ${{ secrets.ASC_KEY_CONTENT }}
        MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
        PERSONAL_GITHUB_ACCESS_TOKEN: ${{ secrets.PERSONAL_GITHUB_ACCESS_TOKEN }}
        USERNAME: ${{ secrets.USERNAME }}
      run: bundle exec fastlane beta

Xcodeのバージョンの選択、CocoaPodsで管理しているライブラリのインストールから始まり、最後のステップで用意したレーンを実行します。

ここまでのステップで用意した環境変数をGitHubのSecretsに設定し、それをenvの項目に記載します。


GitHubでSecretsを設定する

発動のトリガーを以下のようにセットしているので、手動実行(workflow_dispatch)、またはmasterブランチへのプッシュで自動でテストフライトが配布されます。

on: 
  workflow_dispatch:
  push:
    branches:
      - master

最後に

fastlane + AppStoreConnect API + GitHub Actionsを使ってテストフライトの自動化を組んでみました。
GitHub Actionsはmacosで動かすとLinuxの10倍の時間を消費します。ヘビーな使い方をすると無料枠をすぐに超えてしまうので料金には注意してください。

必要に応じてTestFlightが必要になるようなシーンでは今回の構成で十分に便利だと思います。