非エンジニアがClaudeを使って、Google Cloudプロジェクト作成をGASで半自動化してみた

非エンジニアがClaudeを使って、Google Cloudプロジェクト作成をGASで半自動化してみた

Google Cloudプロジェクト作成の手作業をGoogle Apps Scriptで半自動化してみました。コンソール操作のミスを防ぎ、作業効率を上げるために、スプレッドシートから`gcloud`コマンドを自動生成する仕組みを構築した経験をお話しします。
2026.07.24

はじめに

こんにちは、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コマンドの生成とダイアログ表示

メニューを足す

Stage1_GCP.gs
function onOpenGcpTool() {
  SpreadsheetApp.getUi()
    .createMenu(CONFIG.MENU_NAME)
    .addItem(CONFIG.MENU_GENERATE_ITEM, 'generateGcloudCommands')
    .addToUi();
}

メニュー名は定数化しておきました。

Stage1_GCP.gs
const CONFIG = Object.freeze({
  HEADER_ROW: 1,
  MENU_NAME: 'GCPコマンド生成',
  MENU_GENERATE_ITEM: 'gcloudコマンド生成',
});

対象行のチェック

実行時、まず選択行が正しい状態かどうかを見ます。
対象シート以外で実行してる、複数行選んでる、見出し行を選んでる、空行を選んでる、あたりは全てエラーにしました。

Stage1_GCP.gs
if (sheet.getName() !== SHEET_NAMES.PROJECT_LIST) {
  ui.alert('「プロジェクト一覧」シート上で、対象の行を選択してから実行してください。');
  return;
}

if (activeRange.getNumRows() !== 1) {
  ui.alert('複数行が選択されています。対象のデータ行を1行だけ選択してください。');
  return;
}

地味ですが、これがあると「間違えた行で実行しちゃった」を事前に止められます。

プロジェクト名のチェック

Google Cloudのプロジェクト名には制約があるので、その通りにチェックしています。
4〜30文字、英数字と一部記号のみ、末尾は英字か数字、というルールです。

Stage1_GCP.gs
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を機械的に作っています。

Stage1_GCP.gs
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プロジェクト番号が入ってる行は、作成済みと判定して処理を止めます。

Stage1_GCP.gs
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でモーダルに出します。

Stage1_GCP.gs
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とコピーボタンだけの、本当にシンプルな作りです。

CommandDialog.html
<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で完結する方法が見つけられませんでした。
結局ここだけはコンソールで手動で残すことになりました。

<手動で残した手順>

  1. コンソールで請求先アカウント管理画面を開く
  2. 対象の請求先アカウントを選ぶ
  3. 対象プロジェクトIDを探す
  4. 操作メニューから「請求をロック」
  5. 鍵マークが付いたのを確認する

ハマったこと

リリース前に技術者の方にレビューしてもらって、いくつか指摘をもらいました。

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を壁打ち相手にすれば、この程度の業務改善ツールは案外作れるものだな、というのが今回の実感です。

この記事をシェアする

関連記事