CircleCI artifactsのurlをjobのスクリプト内で取得する


導入

Python製のドキュメントツールSphinxで社内ドキュメントを制作するタスクがあった。
各自の手元ではdocker-compose up -d --buildを叩けばsphinx-autobuildで随時更新されるので、よかろうと思っていた。
しかし、Pull Requestのレビューにあたり元のreSTファイルの差分をwebで見たり、手元にpullして見るのは億劫だという意見が出た。
もっともな話である。
幸い、CIツールとして利用していたCircleCIにはartifactsという仕組みがあり、CIのjob内でbuildしたhtmlをホストできた。
では、artifactsのurlをbitbucketのPRにコメント、あるいはslackに通知するためにurlをどう取得するか、転がっている情報が半端だったので記しておきたい。

artifactsへの保存

ビルド アーティファクトの保存 - CircleCIにある通り、artifactsへの保存は非常に簡単だ。circleCIの設定ファイルのstep内でrunと同じくstore_artifactsを記述すれば良い。保存したいファイルまたはディレクトリを設定するだけで設定は完了だ。

.circleci/config.yml
- store_artifacts:
    path: /tmp/artifacts

実際にジョブを回すとartifactsにファイルの一覧がずらっと表示される。
レビュアーにbitbucketのビルドウィジェットなりからCircleCIのjobの結果ページにアクセスしてもらい、artifactsのタブをクリックし、index.htmlへのリンクをクリックしてもらえば、ドキュメントが見られる。
ただ、これはちょっと手間である。

githubであれば、moxciというライブラリがあり、PRに対してメッセージを送ったりslackに投げたりできる。
CircleCIのAPIを叩いてartifactsの一覧を取得し、あらかじめ定めたpathと部分一致するartifactのurlを返すという仕組みのようだ。
artifactsのurlを取得するにあたってはCircleCIのドキュメントにAPIの記述があり、stackoverflowを見てもそうしろと書いてある。
ただ、CircleCIのTokenを取得して環境変数に定義し、arifactsのリストを取得して、そこからurlを抽出する手間が本当に必要だろうか?

artifactのurl

PRごとにCIでStorybookをビルドしてデザイナーとインタラクションまで作っていく話には、

// Artifactsのurlのhostnameは
// "https://4321-12345678-gh.circle-artifacts.com"
// のフォーマットですが、これの "12345678" を確認し、環境変数に入れておきます。
CIRCLE_REPO_ID,

と書かれている。一度artifactsに保存してurlを見、そこから抽出する、これならAPIを叩く必要はない。
但し、初回についてはこの環境変数を設定することができない。
この謎のCIRCLE_REPO_IDとは何なのか。
[メモ] CircleCI_環境変数一覧(1.0&2.0) - Qiitaに存在するどの変数とも違う。類似したフォーマットの変数がいくつかあるので、入れて試してみるという愚行を犯してしまった。他に環境変数がないか出力したが、目的の値は存在しなかった。
Circle Ci 2.0 artifacts url - stackoverflowには

The {UNKNOWN_NUMBER} represents the Github id of the repository.

との回答があった。なるほど。もしかしたらbitbucketでも同様なのではないか。PRへのコメント用にbitbucketのPersonal Settingからアプリパスワードを発行していたのでこのパスワードを認証に使ってみる。
user_name, app_pass, workspace, repositoryは各自置き換えて欲しい。

curl -v -X GET --user <user_name>:<app_pass> --header 'Content-type: application/json' "https://api.bitbucket.org/2.0/repositories/<workspace>/<repository>"

すると、

response.json
{"scm": "git", "website": null, "has_wiki": false, "uuid": "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}",...

というレスポンスが得られる。
このレスポンスのuuidの中括弧を外したものがまさにartifactsのurlに含まれるものに一致した。

結論として、artifactsのurlは

create_url.py
build_number = os.environ.get('CIRCLE_BUILD_NUM')  # CircleCI内で定義されている環境変数
node_index = os.environ.get('CIRCLE_NODE_INDEX')  # CircleCI内で定義されている環境変数

REPOSITORY_UUID = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'  # bitbucket APIで取得した値
VCS_TYPE = 'bb'  # リポジトリのホストサービスコード。GitHubなら'gh'
TARGET_PATH = '/tmp/artifacts/index.html'  # 例。store_artifactsのpath・destinationを参考に決める

url_base = f'https://{build_number}-{REPOSITORY_UUID}-{VCS_TYPE}.circle-artifacts.com/{node_index}' # 基本のurl
url = f'{url_base}{target_path}' # 該当artifactのurl

と表せる。

ブラウザから取得

本来リポジトリのuuidはもっと簡単に手に入りそうな気がする。APIを叩いて取得するのは面倒なので。
試しにbitbucketの該当リポジトリのページ

https://bitbucket.org/<workspace>/<repository>/src/master/

のソースを開いて先ほどのuuidで検索してみると、

window.__initial_state__ = {"section": {"repository": {"connectActions": [], "cloneProtocol": "https", "currentRepository": {"scm": "git", "website": null, "uuid": "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}",...

と引っかかる。やったぜ。
CircleCIで初回設定する際は__initial_state__でソースを検索し、section.repository.currentRepository.uuidの値を利用すると良さそう。

CircleCIの変数でどこか持ってそうな気はするが…。

ちなみに、GitHubだとoctolytics-dimension-repository_idがレポジトリのidらしい。

注意

forkした場合は試していない。artifactsのurlのuuid部分、つまりリポジトリのuuidが変わるなら、moxciのようにCircleCIのAPIを叩く必要がありそう。

参考文献

Sphinx
ビルド アーティファクトの保存 - CircleCI
CircleCIのartifactsを使い倒す
moxci
Get a job's artifacts - CircleCI API (v2)
PRごとにCIでStorybookをビルドしてデザイナーとインタラクションまで作っていく話
CircleCI as a Resource
[メモ] CircleCI_環境変数一覧(1.0&2.0) - Qiita
Circle Ci 2.0 artifacts url - stackoverflow
Returns the object describing this repository. - Bitbucket API