💧

Vapor と Google Cloud Run で実装!App Store ステータスを Slack に投稿する API の作り方💧

2023/05/13に公開

こんにちは。kamimiです。🌞

Vapor で App Store ステータスを Slack に投稿する API を作って Google Cloud Run にデプロイしたので、作り方を書きます。💧

Swift で API を実装するのはサーバーサイドでは主流でないと思います。そのためか記事が豊富ではなく、公式ページを頼りながらなんとか実装しました。。

ちなみに Swift で実装したかったのは、私が iOS エンジニアで Swift が一番使い慣れているからです。

同じようことをやりたい人の参考になれば幸いです。また「もっと良い実装方法あるよ」、「ここ直した方が良いよ」などコメントありましたらご指摘いただけますと幸いです。🙏🏻

作った API の概要

Slack に App Store のステータスを投稿する API です。
投稿したいチャンネル ID と App ID を指定してリクエストすると、以下のように Slack にメッセージが投稿されます。

altテキスト

エンドポイントは1つしかないのですが、勢い余って API Docs を作ったのでよければ見てくださいw

https://kamimi01.github.io/iOSAppReviewStatusCheckerGCP/

またリポジトリはこちらになります。まだまだリファクタリングが必要な状況ですが、ご参考になれば⭐をいただけると嬉しいです!

https://github.com/kamimi01/iOSAppReviewStatusCheckerGCP

実装後に思い直したのですが、この API は API の処理単位としては、ちょっとおかしな感じになっていると思いました。それは当初は API ではなくバッチの想定で実装したからです。。そこはちょっと目を瞑っていただけると幸いです。途中で API として呼びたいニーズが発生したので、APIとして実装することにしました。

また外部の API を呼んでいるだけのこの API の存在意義ですが、これも呼び出し元のニーズです。呼び出し元では実装が難しそな仕様があったため、こうして API として提供することにしたという理由があります。

使用した技術

  • Vapor
  • Google Cloud Run
  • Secret Manager

Vapor

Vapor は Swiftのための 未来の Web フレームワークと謳われています。バックエンドからWebアプリケーションの API と HTTP サーバーを書くことができます。
認証周りや DB アクセスなどをサポートする Swift Package も組み込まれています。公式ドキュメントはわかりやすく、充実しています。

https://vapor.codes/

GitHub での開発も活発なようです。
このリポジトリではないのですが、Vapor 用のライブラリのリポジトリで Vapor Core Team の 0xTim さんとやり取りをしたことがあります。その返信がとても早かったので、比較的活発なのかなと思いました。コミュニティのDiscordもありますね。

https://github.com/vapor/vapor

ちなみに WWDC22 の「Use Xcode for server-side development」のセッション内でも、Vaporが使用されています。

使用した理由は、最初にも書いた通り、Swift でサーバーサイドを実装したかったからですね。

Google Cloud Run

フルマネージドのサーバーレスのプラットフォームです。コンテナイメージをビルドしてデプロイすることができます。
コードを実行する方法としては、Cloud Run サービス と Cloud Run ジョブの2つがあります。私の理解だと API を実装したいなら前者、バッチを実装したいなら後者という使い分けになる認識です。今回は前者を使います。

https://cloud.google.com/run/docs/overview/what-is-cloud-run?hl=ja

使用した理由は、Cloud Run はプログラミング言語の制限がないからです。
例えば Google Cloud Functions を使用する場合、使用できるランタイムは Node.js、Python、Go、Java、Ruby、PHP、.Net Core です。(2023/5/13現在。詳細はこちらを参照ください)つまり、Cloud Functions では Swift で実装することができません。。。
Cloud Run であれば言語の制約を受けないので、使用することにしました。

Secret Manager

API Key やパスワード、証明書のような機密性の高いデータを保存するためのストレージです。環境変数に格納して使用することもできますが、Google Cloud のドキュメントでは環境変数への格納は推奨されていませんので、Secret Manager を使用します。

注意: 環境変数をシークレットの保存と利用に使用しないでください。環境変数は、プロジェクト閲覧者以上の権限を持つユーザーに表示されます。代わりに、シークレットの使用ページの説明に沿って、Cloud Run で Secret Manager を使用してください。

https://cloud.google.com/secret-manager?hl=ja

ここに格納することでデフォルトで暗号化が行われます。またバージョニングの機能もあるので、特定のバージョンの値に固定したりといったことが可能なのだそうです。

API の実装手順

以下の順番で実装していきます。

  1. 事前準備
  2. Vapor プロジェクトを準備する
  3. App API を呼ぶ
  4. App Store State API を呼ぶ
  5. Slack API を呼ぶ

API の処理概要は以下のようになっています。基本的に外部の API(App Store Connect API と Slack API)を呼んでいるだけです。

注意することは、Vapor で外部の API リクエストを行うときは、URLSession ではなく、Vapor が提供している API を使用することです。そうしないとローカルでビルドしているときは良くても、イメージをビルドする時にエラーが発生してしまいます。

https://qiita.com/kamimi01/items/15191e6655906ffcc72b

0. 事前準備

App Store Connect API を使用するための準備

App Store Connect API を使用するための認証情報を発行、取得します。

必要なものは以下です。

  • Issuer ID
  • Key ID
  • Private Key

これらは、Apple Developer Program でキーを生成する時に取得することができます。(作成方法はこちら
Private Key は作成する時一度しかダウンロードできないので、要注意です。 ⚠️

Slack App の準備

Slack App を作成する必要があります。
作成したら、Slack Bot Token を取得しておきます。Slack の API をリクエストするときに必要になります。

1. Vapor プロジェクトを準備する

まずは Vapor のプロジェクトを準備します。

vapor new <プロジェクト名> -n

生成された Package.swift を Xcode で開きます。

2. App Store Connect API の JWT を発行する

App Store Connect API を使用するためには、JWT の発行が必要ですので、その実装をします。

詳しくは以下の記事に書いたので、そちらをご覧ください。

https://qiita.com/kamimi01/items/0676f028670b8ff73c1e

3. App Store Connect API の List Apps を呼ぶ

ようやく App Store Connect API を呼びます。まずは List Apps の API です。

https://developer.apple.com/documentation/appstoreconnectapi/list_apps

この API をリクエストしたい理由は、アプリの名前を取得したいからです。
1アプリのステータスだけを取得したい場合は、わざわざ名前を取得する必要はないと思うのですが、この API は複数アプリのステータスを取得する場合があるので、どのステータスがどのアプリのものかわかりやすいようにしたいというニーズがありました。

この API のレスポンスは以下のようになっています。

{
    "data": [
        {
            "type": "apps",
            "id": "1673161138",
            "attributes": {
                "name": "TopicGen"  // ここが欲しい
            },
            "links": {
                "self": "https://api.appstoreconnect.apple.com/v1/apps/1673161138"
            }
        },
        {
            "type": "apps",
            "id": "1668244395",
            "attributes": {
                "name": "ふれまね"  // ここが欲しい
            },
            "links": {
                "self": "https://api.appstoreconnect.apple.com/v1/apps/1668244395"
            }
        },
// 以下略

4. App Store Connect API の List All App Store Versions for an App を呼ぶ

次に List All App Store Versions for an App の API を呼びます。

https://developer.apple.com/documentation/appstoreconnectapi/list_all_app_store_versions_for_an_app

この API を呼ぶ理由は、App Store でのステータス と そのバージョンを取得したいからです。

レスポンスは以下のようになっています。

{
    "data": [
        {
            "type": "appStoreVersions",
            "id": "0e91618b-37cc-4112-aecc-8a93b84cf6cc",
            "attributes": {
                "versionString": "1.1",  // ここが欲しい
                "appStoreState": "READY_FOR_SALE",  // ここが欲しい
                "createdDate": "2023-02-21T03:51:45-08:00"
            },
// 以下略

5. Slack API の chat.postMessage を呼ぶ

App Store Connect API からのレスポンスを使って、投稿したいテキストに整形したら、最後に Slack に投稿します。

https://api.slack.com/methods/chat.postMessage

デプロイからリリースまでの手順

Google Cloud での操作を行なっていきます。

私は Google Cloud に慣れていないので、GUI からの操作が多いですが、基本的にどの操作も gcloud CLI を使用して行うことができるようです。GUI はよく変わるので、CLIなどを使っての構築の方が他の人が同じ作業を実施したいときの再現性が高くなることは認識してはいますが、今回は許してください。。🙏🏻

0. 事前準備

Google Cloud でプロジェクト作成

Google Cloud でプロジェクトを作成します。

https://cloud.google.com/resource-manager/docs/creating-managing-projects?hl=ja#console

私はコンソールの GUI からぽちぽち操作して作成したのですが、gcloud CLI や API を使っての作成もできるそうです。

gcloud CLI は今後も使うので、セットアップをしておいた方が良いです。

1. ビルドする

イメージをビルドします。最初は20分ちょい時間がかかったので、timeout の時間を 25分に設定したらうまくいきました。

gcloud builds submit --tag gcr.io/{PROJECT_ID}/{IMAGE_NAME} --timeout=25m

2. デプロイする

私の場合、Secret Manager や環境変数の設定をすっ飛ばして、一旦デプロイしてしまいました。

gcloud run deploy --image gcr.io/{PROJECT_ID}/{IMAGE_NAME} --platform managed

新規サービスの場合、↑のコマンドに--set-env-varsのパラメータを設定して環境変数を設定することもできるそうですので、その方が良さそうですね。
私は先にデプロイしてしまったので、ここでは実際に行った手順で書かせていただきました。🙏🏻

3. Secret Manager で変数を設定する

Secret Manager に変数を設定します。今回実装した API の場合、以下の値を Secret Manager に格納しました。

  • Issuer ID
  • Key ID
  • Private Key
  • Slack Bot Token
  • API の Basic 認証で使用する Username
  • API の Basic 認証で使用する Password

https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets?hl=ja

これも私はコンソールの GUI でぽちぽちしながら作成しました。

この時、roles/secretmanager.secretAccessorというロールが付与されていないと、該当のサービスアカウントからこの Secret の値にアクセスできないので、付与を忘れずに行います。

altテキスト

4. 環境変数として Secret Manager の変数を公開設定する

今回、Secret Manager に格納した値は環境変数として公開し、プログラムから読み取ることにしました。

コンソールの「リビジョン」というタブから先ほどデプロイしたリビジョンを選択し、環境変数を追加することができます。

https://cloud.google.com/anthos/run/docs/configuring/environment-variables?hl=ja#console

下の画像のように設定して、「付与」のボタンが出てきた場合は、押します。

altテキスト

(ふと思ったのですが、ドキュメントには Secret Manager の値を環境変数として公開するときのパラメータの指定方法は書いていなかったのですが、できるのかな。時間があったら調べてみます。)

これを6つ分繰り返したら、再度デプロイします。

ちなみに環境変数をプログラム側で取得する実装は以下のようになっています。Vapor を使うととても簡単です。

import Vapor 

let botToken = Environment.get("SLACK_BOT_TOKEN")

これで完成です!!🎉

参考にした記事

https://cloud-ace.jp/column/detail250/

https://tech.medpeer.co.jp/entry/2022/09/05/100000

おわりに

たった1つの API を実装しただけなのですが、個人的には Google Cloud も Vapor も App Store Connect API も初めて使ったので、とても時間がかかってしまいました・・・
あとなぜかサーバーサイドの実装に慣れていないのに、DDD での実装に挑戦したことも時間がかかった要因の一つです。(といってもこれはまだちゃんとできておらず探り探りリファクタリング中なのですが、、)

この API を実装する中で学べたことは多く、また一つできることが増えたので嬉しいですし、時間はかかったものの楽しむことができました。😄

この記事がどなたかの参考になれば幸いです!

Discussion