非エンジニアがClaudeを使って、Google Cloudプロジェクト作成をGASで半自動化してみた
はじめに
こんにちは、Haradaです。
非エンジニアで、普段コードは書かない側の人間です。
チームでは、社内検証用のGoogle Cloudプロジェクトを、希望者向けに作って渡す作業をやっています。
今まではコンソール画面をポチポチとクリックしながら1工程ずつ設定してましたが
今回、作業の効率アップ・作業ミス削減を目的として
Google Apps Script(GAS)で半自動化してみることにしました。
やってることをざっくり書くとこんな感じです。
- スプレッドシートにプロジェクト作成の情報を入れる
- 対象の行を選ぶ
- メニューから「gcloudコマンド生成」を実行する
- 出てきたコマンドをCloud Shellに貼って実行する
- 結果を管理表に書き戻す
作ったもの
スプレッドシートにカスタムメニューを足して、対象行を選んで実行すると、プロジェクト作成用のgcloudコマンドが出てくる、というものです。
メニューはこんな感じです。
GCPコマンド生成
└ gcloudコマンド生成
対象行にはあらかじめ以下の情報を入れておきます。
| 項目 | 内容 |
|---|---|
| 利用者 | プロジェクトを使う人のメールアドレス |
| 請求先アカウント名 | 紐付ける請求先アカウント |
| 請求先アカウントID | 請求先アカウントID |
| プロジェクト名 | 作成するプロジェクト名 |
| 部門名 | 所属部門 |
| プロジェクトID | 作成後に転記 |
| プロジェクト番号 | 作成後に転記 |
部門名からGoogle CloudのフォルダIDを引けるように、別シートに部門リストも用意してあります。

なぜ作ったか
もともとの作業は、申請内容を見ながら管理表に転記し、コンソールで1項目ずつ登録する、という流れでした。
たくさんの件数を対応していく中で、どうしてもミスは発生してしまいます……。
例えば、
- ロールの付与を忘れる
- フォルダの指定を忘れる
- 違うフォルダを指定してしまう
なので、まずはコマンド生成までをGASでやってみることにしました。
GASからGoogle Cloud APIを直接叩くのではなく、あくまでgcloudコマンドを作るだけに留めています。
実行前に人が中身を見られるので、非エンジニアの自分でも「これ何が起きるのか分からないまま実行する」状態を避けられます。
全体の流れ
GASで半自動化したのは、上記の図のC〜Eのあたりです。
対象行のチェック、入力値のバリデーション、コマンド生成、表示までをやってくれます。
GAS側でやっていること
- 対象シート・対象行のチェック
- 必須項目のチェック
- プロジェクト名のバリデーション
- プロジェクトID候補の生成
- 請求先アカウントIDの形式チェック
- 部門名からフォルダIDを取得
- 作成済み行の再実行防止
gcloudコマンドの生成とダイアログ表示
メニューを足す
function onOpenGcpTool() {
SpreadsheetApp.getUi()
.createMenu(CONFIG.MENU_NAME)
.addItem(CONFIG.MENU_GENERATE_ITEM, 'generateGcloudCommands')
.addToUi();
}
メニュー名は定数化しておきました。
const CONFIG = Object.freeze({
HEADER_ROW: 1,
MENU_NAME: 'GCPコマンド生成',
MENU_GENERATE_ITEM: 'gcloudコマンド生成',
});
対象行のチェック
実行時、まず選択行が正しい状態かどうかを見ます。
対象シート以外で実行してる、複数行選んでる、見出し行を選んでる、空行を選んでる、あたりは全てエラーにしました。
if (sheet.getName() !== SHEET_NAMES.PROJECT_LIST) {
ui.alert('「プロジェクト一覧」シート上で、対象の行を選択してから実行してください。');
return;
}
if (activeRange.getNumRows() !== 1) {
ui.alert('複数行が選択されています。対象のデータ行を1行だけ選択してください。');
return;
}
地味ですが、これがあると「間違えた行で実行しちゃった」を事前に止められます。
プロジェクト名のチェック
Google Cloudのプロジェクト名には制約があるので、その通りにチェックしています。
4〜30文字、英数字と一部記号のみ、末尾は英字か数字、というルールです。
function validateProjectName(name) {
const errors = [];
if (!name) {
errors.push('プロジェクト名が空です');
return errors;
}
if (name.length < PROJECT_NAME_MIN_LENGTH || name.length > PROJECT_NAME_MAX_LENGTH) {
errors.push(
'プロジェクト名は' + PROJECT_NAME_MIN_LENGTH + '〜' + PROJECT_NAME_MAX_LENGTH +
'文字にしてください'
);
}
if (!/^[a-zA-Z0-9' \-!]+$/.test(name)) {
errors.push(
'プロジェクト名に使用できない文字が含まれています'
);
}
if (!/[a-zA-Z0-9]$/.test(name)) {
errors.push('プロジェクト名の末尾は英字か数字にしてください');
}
return errors;
}
申請の時点で_や.や:が紛れ込んでたら、ここで止まるようにしています。
プロジェクトID候補の生成
プロジェクト名から、それっぽいIDを機械的に作っています。
function generateProjectIdCandidate(name, row) {
let id = name.toLowerCase();
id = id.replace(/[^a-z0-9]+/g, '-');
id = id.replace(/-+/g, '-');
id = id.replace(/^-+/, '').replace(/-+$/, '');
if (!/^[a-z]/.test(id)) {
id = 'p-' + id;
}
if (id.length < PROJECT_ID_MIN_LENGTH) {
id = id + '-' + String(row);
}
id = id.replace(/-+/g, '-').replace(/-+$/, '');
while (id.length < PROJECT_ID_MIN_LENGTH) {
id += '0';
}
if (id.length > PROJECT_ID_MAX_LENGTH) {
id = id.substring(0, PROJECT_ID_MAX_LENGTH).replace(/-+$/, '');
}
return id;
}
ただ、このIDがすでに使われていないかまではGAS側では確認していません。なので確認ダイアログには一言添えています。
※このIDが既に使われていないかは未確認です。
実際の作成時に重複エラーが出た場合は、末尾に数字を加える等して調整してください。
作成済み行の再実行防止
すでにプロジェクトIDかプロジェクト番号が入ってる行は、作成済みと判定して処理を止めます。
if (existingProjectId || existingProjectNumber) {
errors.push(
'この行には既に「' + HEADERS.PROJECT_ID + '」または「' +
HEADERS.PROJECT_NUMBER + '」が入力されています。作成済みの可能性があるため処理できません。'
);
}

gcloud projects createが失敗すれば後続も失敗しますが、それより前、誤操作の段階で止めたかったので入れています。
生成されるコマンド
生成したコマンドで、プロジェクト作成・請求先アカウントへの紐付け・API有効化・IAMロール付与・予算アラート作成あたりまで一通りやってしまいます。
set -e
set -u
set -o pipefail
LOG_FILE=~/gcp_provision_project-id_$(date +%Y%m%d%H%M%S).log
(
gcloud projects create 'project-id' \
--name='Project Name' \
--folder='123456789012'
gcloud billing projects link 'project-id' \
--billing-account='XXXXXX-XXXXXX-XXXXXX'
gcloud services enable \
billingbudgets.googleapis.com \
monitoring.googleapis.com \
--project='project-id'
gcloud projects add-iam-policy-binding 'project-id' \
--member='user:user@example.com' \
--role='roles/owner'
gcloud billing budgets create \
--billing-account='XXXXXX-XXXXXX-XXXXXX' \
--display-name='budget_project-id' \
--budget-amount=5000JPY
PROJECT_NUMBER=$(gcloud projects describe 'project-id' --format='value(projectNumber)')
printf "%s\t%s\n" 'project-id' "$PROJECT_NUMBER"
) 2>&1 | tee "$LOG_FILE"
冒頭の3行は結構大事にしてます。
set -e
set -u
set -o pipefail
途中のコマンドがコケたら、そこで止まってほしいからです。
プロジェクト作成に失敗してるのに請求紐付けやIAM付与が続いてしまうと、後片付けが面倒だからです。
実行ログもファイルに残るようにしてあります。
コマンドの表示
生成したコマンドは、GASのHTML Serviceでモーダルに出します。
function showCommandDialog_(ui, commandText) {
const template = HtmlService.createTemplateFromFile('CommandDialog');
template.commandText = commandText;
const htmlOutput = template.evaluate().setWidth(900).setHeight(600);
ui.showModalDialog(htmlOutput, 'gcloudコマンド (Stage 2 自動生成)');
}
HTML側はtextareaとコピーボタンだけの、本当にシンプルな作りです。
<textarea id="cmdText" readonly><?= commandText ?></textarea>
<button type="button" onclick="copyText()">コピー</button>
<span id="copied">コピーしました</span>
内容を確認してコピーして、Cloud Shellに貼って実行、という流れです。

請求ロックだけはCLIで見つけられなかった
プロジェクト作成や請求先アカウントとの紐付けはgcloudで問題なくできました。
gcloud billing projects link 'project-id' \
--billing-account='XXXXXX-XXXXXX-XXXXXX'
ただ、紐付けの後にやる請求ロックだけは、CLIで完結する方法が見つけられませんでした。
結局ここだけはコンソールで手動で残すことになりました。
<手動で残した手順>
- コンソールで請求先アカウント管理画面を開く
- 対象の請求先アカウントを選ぶ
- 対象プロジェクトIDを探す
- 操作メニューから「請求をロック」
- 鍵マークが付いたのを確認する
ハマったこと
リリース前に技術者の方にレビューしてもらって、いくつか指摘をもらいました。
1. OAuthスコープに不要な権限が入っていた
正直「OAuthスコープって何?おいしいの?」状態だったので、ここもClaudeに聞きました。
「GASに、どこまで操作していいかを許可するための権限リスト」のことで、
要は、いらない許可は消しといた方が安全、という指摘でした。
appsscript.jsonに入ってたscript.storageスコープ、今回のコードでは使ってなかったので削りました。
2. フォルダID取得失敗時にエラーが二重に出る
フォルダID取得に失敗した後も、後続のチェックがそのまま走ってしまって、似たエラーが2回出る状態になってました。
失敗したら後続はスキップするように直しました。
3. トリガーの判定が甘かった
GASのトリガーは、雑にいうと「いつこの処理を動かすかの予約」です。
関数名だけで判定してたのを、ON_OPEN(スプレッドシートを開いた時に動く)かどうかも見るようにしました。
同じ関数名で別種類のトリガーが登録されてても誤判定しないようにするためです。
Claudeに助けてもらったこと
今回もかなりの部分をClaudeに頼りました。
- GAS全体の構成
- スプレッドシートの行・列の扱い
- 入力バリデーション
gcloudコマンドの組み立て- シェルエスケープ
- レビュー指摘の意味が分からない時の解説
この辺りは特にお世話になりました
EventType.ON_OPENの話もOAuthスコープの話も、最初は言葉から意味不明だったので、分かるまでその場で壁打ちしながら進めた感じです。
最終的にはGitHubで人にレビューしてもらって、指摘を直してLGTMをもらってマージしています。
AIに相談することと、人にレビューしてもらうこと、両方やることが大事だと思いました。
まとめ
GASから直接Google Cloudを触るのではなく、あくまでgcloudコマンドを作るところまでにして
実行は人がCloud Shellでやる、という形にしました。
完全自動化ではないですが、手作業のミスは減らせて、運用にも乗せやすくなりました。
請求ロックのようにCLIでどうにもならなかった部分は素直に手動で残しましたが、それでも十分効果はあると思っています。
非エンジニアでも、Claudeを壁打ち相手にすれば、この程度の業務改善ツールは案外作れるものだな、というのが今回の実感です。







