AppStoreConnectAPIを使ってXcode Cloudのビルドワークフローをトリガーする
この記事はLuup Advent Calendarの11日目の記事です。
こんにちは、iOSエンジニアの大瀧です。
今回はAppStoreConnectAPIを使ってXcode Cloudのビルドワークフローをトリガーする方法について書いていきます。
背景
LUUPのiOS開発ではCI/CDにXcode Cloudを活用しています。普段の開発でQAや動作確認をする際、ブランチ毎にTestflightへ配信することが多々あると思います。しかし、AppStoreConnectAPI経由でのXcode Cloudのビルドに関しては公式ドキュメントや記事が充実していません。実装当時、苦労したのでその方法について書いていきます。
実装
まず、Xcode CloudのビルドワークフローをトリガーするAPIはこれです。
POST https://api.appstoreconnect.apple.com/v1/ciBuildRuns
このAPIのリクエストにはscmGitReferenceID
とciWorkflowID
が必要みたいです。
{
"type": "ciBuildRuns",
"attributes": { },
"relationships": {
"sourceBranchOrTag": {
"data": {
"type": "scmGitReferences",
"id": <SCM_GIT_REFERENCE_ID>,
},
},
"workflow": {
"data": {
"type": "ciWorkflows",
"id": <CI_WORKFLOW_ID>,
},
},
},
}
まずciWorkflowID
についてですが、単純にXcode Cloudの各ワークフローのIDです。APIでも取得可能ですが、AppStoreConnectからワークフローを編集する画面を開くと以下のようなURLになっているのでそちらからもworkflowIDが取得可能です。
https://appstoreconnect.apple.com/teams/<team_id>/apps/<app_id>/ci/workflows/<workflow_id>
scmGitReference
については、公式の説明を見てもいまいちパッとしないのですが、自分はGitのコミットを特定するIDみたいなものと解釈してます。
このscmGitReference
はブランチを指定してビルドしたい場合に必要となります。ちなみにブランチ指定せずにビルドするとデフォルトブランチが指定されます。
そして特定のGitリポジトリのscmGitReferenceはこちらのAPIで取得できます。
GET https://api.appstoreconnect.apple.com/v1/scmRepositories/{REPOSITORY_ID}/scmGitReferences
レスポンスはブランチごとにscmGitReferenceが配列で返ってきますのでビルドしたいブランチのIDを取得します。
これをSCM_GIT_REFERENCE_ID
として指定してあげれば対象のブランチをビルドできます。
{
"data": [
{
"type": "scmGitReferences",
"id": "XXXXXX-XXXX-XXXX-XXXXXXXXX",
"attributes": {
"name": "develop",
"isDeleted": false,
"kind": "BRANCH",
"canonicalName": "refs/heads/develop"
},
"links": {
"self": "https://api.appstoreconnect.apple.com/v1/scmGitReferences/XXXXX-XXXX-XXXXX-XXXXXXXXXX"
}
},
...
],
"links": { ... },
"meta": { ... }
}
REPOSITORY_ID
はこちらのAPIで取得できます。
GET https://api.appstoreconnect.apple.com/v1/scmRepositories
これでXcode Cloudのビルドワークフローをトリガーするのに必要な情報が揃いました。あとはciBuildRuns
を実行するだけです!
上記のAPIを使って、TypescriptでAPI化した例がこちらです。
router.post("/", async function (req: Request, res: Response) {
// APIで取得可、特定リポジトリであればハードコーディングでもOK
const scmRepositoriesReferenceId = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX"
// APIで取得可、特定ワークフローであればハードコーディングでもOK
const workflowId = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX"
// ビルドしたいブランチをリクエストで送る
const branchName = req.body.branchName
try {
const token = await generateJWT()
const scmGitReferenceId = await fetchScmGitReferenceId(token, branchName, scmRepositoriesReferenceId)
await buildXcodeCloud(token, scmGitReferenceId, workflowId)
} catch (error) {
res.send(error)
}
})
const fetchScmGitReferenceId = async (token: string, branchName: string, scmRepositoriesReferenceId: string): Promise<string> => {
const response = await fetch(
`https://api.appstoreconnect.apple.com/v1/scmRepositories/${scmRepositoriesReferenceId}/gitReferences`,
{
method: "GET",
headers: { "Authorization": `Bearer ${token}` },
}
)
const json = await response.json() as any
const branch = json.data.find((x) => x.attributes.name === branchName)
return branch.id
}
const buildXcodeCloud = async (token: string, scmGitReferenceId: string, ciWorkflowId: string) => {
await fetch(
"https://api.appstoreconnect.apple.com/v1/ciBuildRuns",
{
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
data: {
"type": "ciBuildRuns",
"attributes": {},
"relationships": {
"sourceBranchOrTag": {
"data": {
"type": "scmGitReferences",
"id": scmGitReferenceId,
},
},
"workflow": {
"data": {
"type": "ciWorkflows",
"id": ciWorkflowId,
},
},
},
},
}),
})
}
最後にこのAPIをGitHub Actionsのコメントトリガーで叩くなど好きなタイミングで実行すればXcode Cloudのビルドワークフローをビルドできると思います!
最後に
Luupでは常に最新の技術のキャッチアップ&導入にチャレンジしています。
事業自体の進化に追従しながらエンジニアにとってより良い開発環境を構築できるように日々精進しています。
課題と挑戦がいっぱいのLuupでの開発に少しでも興味を持っていただけた方は、是非お気軽にご連絡ください!
Discussion