はじめに
開発者体験:DXをめちゃくちゃ改善した話 Advent Calendar 2021の22日目です!
弊社で作ってめっちゃ効率上がったツールの話です
経緯
ビルドについて
会社でUnity製モバイルアプリを開発しているのですが、ビルドを全てJenkinsマシンで自動化していました。
最近チーム規模の拡大等の理由により、ビルドマシン1台では待ちが発生してきたためビルドマシンを2台増やし、MacMini3台構成にしました。
マシンが増えたことで起きてしまう問題
マシンが増えて高速でビルドができるようになり解決!と思っていたのですが、思っていなかったところでめんどくさい作業が発生してしまいました。
ProvisioningProfileの更新です。
弊社ではメンバーの新規ジョイン・業務委託メンバーの追加、既存メンバーの機種変更などのタイミングで新しい端末をProvisioningProfileに追加し、DLし、Xcodeに追加する という作業を 温かみのある手作業で行っていました
端末の追加とProvisioningProfile自体の端末リストの更新はまだ1回だけの作業なので良いのですが、弊社の開発するアプリは ProvisioningProfileの数が10数個あり、これらを3台のMacMiniでAppleDeveloperを開き、ProvisioningProfileを1個づつDLし、クリックして開いてXcodeへ登録という地味に面倒な上に妙に神経使う作業が発生してしまいました
正直エンジニア以外でも出来る作業やろ!とは思いつつ、AppleDeveloperをエンジニア以外が弄るのはインシデントにつながる恐れがあるためなかなか他の人にお願いすることもできません
そもそも頼まれた他の人が苦しむだけなので負の連鎖です。
自動化や!
調べてみた所AppStoreConnectでの操作を行えるAPI、AppStoreConnectAPIというものがありました(そのまんまな命名。。)
https://developer.apple.com/jp/app-store-connect/api/
リファレンスを見てみるとProvisioningProfileがDLできそうな雰囲気です
これを使い、ProvisioningProfileを全件DLし、自動で開くシェルスクリプトを作れば業務効率化待ったなし!
早速やってみる(準備編)
AppStoreConnectAPIを使うにはAPIを叩くためのKeyが3種類必要です
- Issuer ID
- キーID
- 秘密鍵ファイル(p8ファイル)
AppStoreConnectのユーザーとアクセス→キー→AppStoreConnectAPIから発行できるので発行しておきましょう。
もし表示されない場合はアカウントの権限が足りない可能性があるので別途上司などに依頼しましょう。
早速やってみる(Python編)
今回は実際にAPI叩き、ファイルを保存する処理をPythonで書いてみました
AppStoreConnectAPIを叩くサンプルとしてはこちらを参考にしました
https://gist.github.com/corrieriluca/3c9f062401582dad83013d7363741b0c
import os
import base64
import requests,time
from authlib.jose import jwt
KEY_ID = ""
ISSUER_ID = ""
#通信有効期限は20分程度なのです。
EXPAIR_TIME = int(round(time.time() + (20.0 * 60.0)))
P8_FILE = ".p8 filepath."
KEY_ID = "hogehoge"
ISSUER_ID = "fugafugafuga"
#認証ファイル
with open(P8_FILE, "r") as file:
private_key = file.read()
header = {
"alg" : "ES256",
"kid" : KEY_ID,
"typ" : "JWT"
}
payload = {
"iss" : ISSUER_ID,
"exp" : EXPAIR_TIME,
"aud" : "appstoreconnect-v1"
}
token = jwt.encode(header,payload,private_key)
JWT = "Bearer " + token.decode()
URL = "https://api.appstoreconnect.apple.com/v1/profiles"
HEAD = {"Authorization": JWT}
#API叩いてProvisioningを取得
#もう使ってない(必要ない)Profile落とさないように検索をかける
res = requests.get(URL,params={"filter[name]": "検索したいProvisioningProfileの名前","limit": 50},headers=HEAD)
os.makedirs("provisioning", exist_ok=True)
json = res.json()
#jsonにしてdataの配列を回す
data_array = json["data"]
for data in data_array:
attr = data["attributes"]
#無効はSkip
if attr["profileState"] == "INVALID":
continue
name = attr["name"]
#profileContentがファイルの中身なのでこれをDecodeしたものをファイルとして保存してやる
content = attr["profileContent"]
write_file = open("provisioning/" + name + ".mobileprovision","bw")
write_file.write(base64.b64decode(content))
write_file.close()
大事なところとしては
res = requests.get(URL,params={"filter[name]": "検索したいProvisioningProfileの名前","limit": 50},headers=HEAD)
ここでAPIを叩いています。filter[name]でProvisioningProfile取得する際に名前でフィルターをかけています。
弊社では開発中のプロダクトのProvisioningProfileのみ落としたいので利用しています
for data in data_array:
attr = data["attributes"]
#無効はSkip
if attr["profileState"] == "INVALID":
continue
ここで取得したProvisioningをforで回しながら、チェックをしていきます
何らかの事情により(例:証明書期限切れ)ProvisioningProfileが無効になっていた場合、落としても意味が無いのでスキップしています
["profileState"]の中にVALID,INVALIDで値が入っているのでチェックして弾くようにします。
実際に保存しているのがこの辺の処理です
name = attr["name"]
#profileContentがファイルの中身なのでこれをDecodeしたものをファイルとして保存してやる
content = attr["profileContent"]
write_file = open("provisioning/" + name + ".mobileprovision","bw")
write_file.write(base64.b64decode(content))
write_file.close()
バイナリファイルとして保存するのでopenを呼ぶときのモードはbwにしておきましょう(1敗)
このpythonファイルを実行するとこのような結果になります
$ python main.py
フォルダが生成されProvisioningProfileが大量にDLされました🎉🎉🎉
早速やってみる(シェルスクリプト編)
最後にこのpythonファイルを実行し、ProvisioningProfileをひたすら開くシェルを作成します
といっても超簡単です
python3 main.py
for file in provisioning/*; do
echo $file
open $file
done
forでファイル名を回してopenコマンドで開いているだけのシンプルな物です。
あとはこの shellを必要なタイミングで叩くだけでローカルのProvisioningProfileを最新の状態にすることが出来ます!
作ってみて
前々からやりたかった自動化だったのですが実際にやってみてその便利さに泣きました(マジ)
端末を買い替えた人が出たときや検証端末を増やす際も、AppleDeveloperでProvisioningProfileの更新をしてしまえば各ビルドマシンにSSHとかで入ってshell叩くだけになったので数時間単位で業務時間の削減になりました🎉
あと、結構属人化しがちだった作業だったのですがシェルスクリプト叩ける人なら誰でも出来る(つまりエンジニアなら全員出来る)ようになったので属人化防止にも繋がりました(流石にAppleDeveloper周りは人を選ぶけど。。)