この記事で扱うこと
wrangler deploy の前に account_id を確かめる小さな Bash スクリプトで、本来止めるべき設定を通してしまう不具合が2つ見つかりました。再現手順、原因、修正前後の差分だけをまとめます。
再現手順
適当な作業ディレクトリで、修正前スクリプト(check-account.sh)に次の2ファイルを渡します。
$ printf "account_id = '0123456789abcdef0123456789abcdef'\n" > single.toml
$ ./check-account.sh single.toml
OK: account_id=account_id = '0123456789abcdef0123456789abcdef'
$ printf '[env.prod]\naccount_id = "prod222"\n' > envonly.toml
$ ./check-account.sh envonly.toml
OK: account_id=prod222
どちらも終了コード 0 です。1つ目は値の中に行全体が入っており、2つ目はトップレベルに account_id が無いのに通っています。
原因
-
sed は一致しないと入力をそのまま出す。 抽出用の正規表現が
"..."しか想定しておらず、TOML で正しい書き方の'...'(リテラル文字列)では置換が起きません。後段は「空かどうか」しか見ないので通過します。 -
grep がファイル全体を検索している。
[env.prod]などセクション内の行も拾うため、トップレベルの指定の有無を判定できていません。 - さらに根本的には、取り出した値が実在する自分のアカウントかを確かめていませんでした。抽出ミスも打ち間違いも、ここで止められたはずです。
修正前後の差分
@@ -1,18 +1,22 @@
#!/usr/bin/env bash
-# デプロイ前のフェイルセーフ: 設定ファイルに account_id があり、
-# CLOUDFLARE_ACCOUNT_ID が設定されている場合は一致していることを確認する。
-# 使い方: ./check-account.sh [wrangler.toml|wrangler.jsonc]
+# デプロイ前のフェイルセーフ(v2): 設定ファイルの account_id が、
+# いまログインしている Wrangler のアカウント一覧(wrangler whoami)に含まれるか確認する。
+# 使い方: ./check-account-v2.sh [wrangler.toml|wrangler.jsonc]
set -euo pipefail
cfg="${1:-wrangler.toml}"
+WRANGLER="${WRANGLER:-npx wrangler}" # テスト時はモックに差し替え可能
[ -f "$cfg" ] || { echo "NG: $cfg が見つかりません" >&2; exit 1; }
-id="$(grep -E '^[[:space:]]*"?account_id"?[[:space:]]*[=:]' "$cfg" | head -n1 \
- | sed -E 's/.*[=:][[:space:]]*"([^"]+)".*/\1/' || true)"
-if [ -z "$id" ]; then
- echo "NG: $cfg に account_id がありません(誤アカウントへのデプロイを防ぐため必須)" >&2
- exit 1
-fi
+scope="$(awk '/^[[:space:]]*\[/{exit} {print}' "$cfg")" # 最初の [section] より前だけ
+case "$cfg" in *.json|*.jsonc) scope="$(cat "$cfg")";; esac
+id="$(printf '%s\n' "$scope" \
+ | grep -E '^[[:space:]]*"?account_id"?[[:space:]]*[=:]' | head -n1 \
+ | sed -nE "s/.*[=:][[:space:]]*[\"']([^\"']+)[\"'].*/\1/p" || true)"
+[ -n "$id" ] || { echo "NG: $cfg のトップレベルから account_id を読み取れません" >&2; exit 1; }
if [ -n "${CLOUDFLARE_ACCOUNT_ID:-}" ] && [ "$CLOUDFLARE_ACCOUNT_ID" != "$id" ]; then
- echo "NG: CLOUDFLARE_ACCOUNT_ID と $cfg の account_id が一致しません" >&2
- exit 1
+ echo "NG: CLOUDFLARE_ACCOUNT_ID と $cfg の account_id が一致しません" >&2; exit 1
+fi
+who="$($WRANGLER whoami 2>&1)" || { echo "NG: wrangler whoami が失敗しました(未ログイン?)" >&2; exit 1; }
+if ! printf '%s\n' "$who" | grep -qwF -- "$id"; then
+ echo "NG: account_id=$id はログイン中の認証情報で操作できるアカウントにありません" >&2; exit 1
fi
-echo "OK: account_id=$id"
+echo "OK: account_id=$id(wrangler whoami で確認)"
変更点:
-
awkで最初の[section]より前だけを対象にした(jsonc は従来どおり全体)。 - sed を
-n ... /pにし、一致しなかったら空になるようにした。クォートは"と'の両方を許可。 -
wrangler whoamiの出力に、そのaccount_idが単語として含まれるかを確認。ログイン中の認証情報で扱えないアカウントなら止まる。未ログインで whoami が失敗した場合も止まる。
動作確認
ログインなしでも再現できるよう、wrangler whoami をアカウント表を返すモック(WRANGLER=./mock-wrangler)に差し替えて9ケースを検証しています。CIに組み込む前に、実際の認証情報で一度動作を確かめてください。
== ok.toml / single.toml / c.jsonc / commented.toml
OK: account_id=0123456789abcdef0123456789abcdef(wrangler whoami で確認)
== envonly.toml
NG: envonly.toml のトップレベルから account_id を読み取れません
== none.toml
NG: none.toml のトップレベルから account_id を読み取れません
== other.toml # account_id = 'aaa111'(whoami の一覧に無い)
NG: account_id=aaa111 はログイン中の認証情報で操作できるアカウントにありません
== 未ログイン(モックが exit 1)
NG: wrangler whoami が失敗しました(未ログイン?)
残っている制限
- jsonc で
env内のaccount_idがトップレベルより前にあると、そちらを読みます。厳密に扱うならコメントを除去してjq '.account_id'で読む方法があります。 - whoami の出力形式は Wrangler のバージョンで変わる可能性があります。ここでは表の中にIDが単語として含まれるかだけを見ています。
参考
- Wrangler コマンド(whoami): https://developers.cloudflare.com/workers/wrangler/commands/
- Wrangler configuration: https://developers.cloudflare.com/workers/wrangler/configuration/
- TOML v1.0.0(リテラル文字列): https://toml.io/ja/v1.0.0