🍎

AppStoreConnectAPIを使ってXcode Cloudのビルドワークフローをトリガーする

2023/12/11に公開

この記事はLuup Advent Calendarの11日目の記事です。

こんにちは、iOSエンジニアの大瀧です。

今回はAppStoreConnectAPIを使ってXcode Cloudのビルドワークフローをトリガーする方法について書いていきます。

背景

LUUPのiOS開発ではCI/CDにXcode Cloudを活用しています。普段の開発でQAや動作確認をする際、ブランチ毎にTestflightへ配信することが多々あると思います。しかし、AppStoreConnectAPI経由でのXcode Cloudのビルドに関しては公式ドキュメントや記事が充実していません。実装当時、苦労したのでその方法について書いていきます。

実装

まず、Xcode CloudのビルドワークフローをトリガーするAPIはこれです。

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

POST https://api.appstoreconnect.apple.com/v1/ciBuildRuns

このAPIのリクエストにはscmGitReferenceIDciWorkflowIDが必要みたいです。

Request sample
{
  "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で取得できます。

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

GET https://api.appstoreconnect.apple.com/v1/scmRepositories/{REPOSITORY_ID}/scmGitReferences

レスポンスはブランチごとにscmGitReferenceが配列で返ってきますのでビルドしたいブランチのIDを取得します。
これをSCM_GIT_REFERENCE_IDとして指定してあげれば対象のブランチをビルドできます。

Response sample
{
    "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での開発に少しでも興味を持っていただけた方は、是非お気軽にご連絡ください!
https://recruit.luup.sc

Luup Developers Blog

Discussion