Fastlane で AppStoreConnect にアップロードするまで 


この記事は、フラー株式会社 [Advent Calendar 2021](https://qiita.com/advent-calendar/2021/fuller-inc) の7日目の記事です。

6日目の記事は @zzzaaawwwaaa さんによる [Rubyやってた人間がGo始めて思ったこと](https://qiita.com/zzzaaawwwaaa/items/9bc95826556beb4bb9c8) でした。

# 概要

[Fastlane 2.197.0](https://github.com/fastlane/fastlane/releases/tag/2.197.0) で dSYM のダウンロードが App Store Connect API 対応しました。
これで証明書のダウンロードからストアへのアップロード、dSYM ダウンロードなど一通り CI で利用する処理が API 経由で利用可能になったので、おさらいしようと思い、記事にしました。

## 準備

Xcode プロジェクトを作成して、[Apple Developer Portal](https://developer.apple.com/account/resources/identifiers/add/bundleId) で App Identifier を作成します。
App Store Connect で iOS アプリの作成し [ユーザとアクセス > キー](https://appstoreconnect.apple.com/access/api/new) から App Store Connect Key を作成します。
Fastlane の [ドキュメント](https://docs.fastlane.tools/app-store-connect-api/) で示されている通り、作成した鍵をダウンロードし、key_id, issuer_id と合わせて以下の json ファイルを作成します。

```json
{
  "key_id": "D383SF739",
  "issuer_id": "6053b7fe-68a8-4acb-89be-165aa6465141",
  "key": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHknlhdlYdLu\n-----END PRIVATE KEY-----",
  "duration": 1200,
  "in_house": false
}
```

### Fastlane

Fastlane を Bundler でインストールします。

```ruby:Fastfile
$ bundle init
$ bundle add fastlane
$ bundle exec fastlane init
```

### 証明書の作成

`fastlane/Fastfile` が生成されるので、`match` の設定を行います。

GitHub で証明書配置用のリポジトリを作成し、Fastfile に プロビジョニングプロファイル作成用の lane を追加します。
API Key の指定方法は複数ありますが、ここでは json ファイルを指定する方式を利用します。
前述のファイルを配置し、ファイルパスを match の引数に指定します。

```ruby:Fastfile
  lane :renew_profile do
    match(
      app_identifier: '{アプリの bundle ID}',
      api_key_path: '{json ファイルのパス}',
      type: 'appstore',
      git_url: '{作成した証明書用のレポジトリの Git URL}'
    )
  end
```

この lane はプロビジョニングプロファイルの更新時にも利用できるので、lane として定義しておけば端末登録後などに便利です。

```bash
$ bundle exec fastlane renew_profile
```

実行すると証明書とプロビジョニングプロファイルが作成されます。最初に実行したとき、証明書の暗号化用のパスワードを設定する必要があります。

### App Store Connect へアップロード

Fastlane でビルドし、ipa ファイルをストアにアップロードする lane を作成します。

```bash
  lane :upload do
    match(
      app_identifier: '{アプリの bundle ID}',
      type: 'appstore', 
      readonly: true,
      git_url: '{作成した証明書用のレポジトリの Git URL}'
    )
    gym(
      export_method: 'app-store'
    )
    deliver(
      app_identifier: '{アプリの bundle ID}',
      api_key_path: '{json ファイルのパス}',
      skip_metadata: true,
      skip_screenshots: true,
      precheck_include_in_app_purchases: false
    )
  end
```

### dSYM のダウンロード

App Store Connect に ipa のアップロードが完了し、処理が完了したら dSYM をダウンロードできるようになります。
以下の lane を作成しておき、fastlane で簡単にダウンロードできるようにしておきます。

```ruby:Fastfile
  lane :donwload_dsym do
    download_dsyms(
      app_identifier: '{アプリの bundle ID}',
      api_key_path: '{json ファイルのパス}',
      version: "latest"
    )
    # Firebase Clashlitics などにアップロード
  end
```

## CI の利用

作成した3つの lane のうち、ストアでアップロードする処理を CI から実行させてみます。
ここでは GitHub Actions を利用します。

### CI 用の Keychain の設定

Fastfile に　CI で　Match を動かすため、以下の設定を追加します。

```ruby:Fastfile
  before_all do
    if is_ci?
      create_keychain(
          name: 'fastlane_tmp_keychain',
          default_keychain: true,
          unlock: true,
          timeout: 3600,
          lock_when_sleeps: true,
          password: '',
          add_to_search_list: true
        )
    end
  end
```

### App Store Connect API Key の配置

リポジトリの設定から Secrets を追加します。適当な名前を付け、json ファイルの中身を貼り付け、その環境変数をファイルに出力する処理を GitHub Actions で行います。

```yml
    - name: Export App Store Connect API Key
      run: |
        cat << "EOF" > appstore-connect-key.json
        ${{ secrets.APP_STORE_CONNECT_API_KEY_JSON }}
        EOF
```

### SSH 鍵の設定

`Fastlane Match` では、証明書管理用のリポジトリを clone するので、アクセスできるように設定する必要があります。
ssh キーペアを `ssh-keygen` で作成し、証明書管理用のリポジトリの Deploy Key に public key を、アプリ側のリポジトリの secrets に秘密鍵をそれぞれ登録し、GitHub Actions からその鍵を利用するように設定します。

### GitHub Actions のワークフロー

App Store Connect にアップロードするワークフローを作成します。

```yml:.github/upload.yml
on:
  push:
    branches:    
      - main

jobs:
  build:
    runs-on: macos-latest

    steps:
    - uses: actions/checkout@v2
      
    - name: Bundle Install
      run: bundle install

    - name: Export App Store Connect API Key
      run: |
        cat << "EOF" > appstore-connect-key.json
        ${{ secrets.APP_STORE_CONNECT_API_KEY_JSON }}
        EOF

    - name: Export SSH Key
      run: |
        cat << "EOF" > ssh_private_key
        ${{ secrets.SSH_PRIVATE_KEY }}
        END

    - name: Upload To App Store
      run: |
        chmod 600 ssh_private_key
        bundle exec fastlane upload
      env:
        GIT_SSH_COMMAND: ssh -i ssh_private_key -o StrictHostKeyChecking=no
        MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
```

上記のワークフローでは、以下の3つの Secrets をリポジトリの設定から登録する必要があります。

- `APP_STORE_CONNECT_API_KEY_JSON`: API キーを Json 形式で貼り付ける
- `SSH_PRIVATE_KEY`: 作成した秘密鍵を貼り付ける
- `MATCH_PASSWORD`: match の初回実行時に設定したパスワード

※ main ブランチの更新時にビルドとアップロードを行う例です。

## 終わりに

今までは iOS アプリ開発の CI といえば、Bitrise が主流だったと思いますが、今年8月に新プランによる価格改定がなされ、2022年8月頃から従来のプランが利用できなくなります。
今までが安かった分、従来のプランより高額になるケースがほとんどだと思います。
今のうちに CI 周りの整理をしておくと、いざ乗り換える必要がでたときにも安心ですね。

## 参考

- https://docs.fastlane.tools
- https://qiita.com/koogawa/items/ddf86b56d6b82ae11473
- https://zenn.dev/chocolate/articles/633f6aa5d9a18db2e52f
- https://www.bitrise.io/pricing