HCP Terraformの利用していないWorkspaceを一括削除するスクリプトを作成してみた
私は一時的な検証環境をHCP Terraformで作成することが多いです。
HCP Terraformにはephemeral workspacesという機能があり、一定期間利用していないリソースを削除してくれます。
リソースは削除してくれるのですが、Workspaceは残ります。
HCP Terraformの場合は、Workspaceの数自体は課金に関わらないため問題ないのです。
しかし、使わなくなったWorkspaceが沢山あると気になります。
今回はProject内の使わなくなったWorkspace(リソース数0)を一括削除するスクリプトを作成してみました。
作成したスクリプト
作成したスクリプトは以下です。
HCP Terraform APIを利用しています。
指定したProject内のWorkspaceのうちリソース数0のWorkspaceを取得し表示します。
--force
オプションを付けて実行すると、上記に加えてリソース数0のWorkspaceの削除まで行います。
#!/bin/bash
# HCP Terraformの特定プロジェクト内でリソースが0のワークスペースを一括削除するスクリプト
set -euo pipefail
# 設定(環境変数または デフォルト値)
ORGANIZATION="${HCPTF_ORGANIZATION:-default-org}"
PROJECT="${HCPTF_PROJECT:-default-project}"
API_BASE_URL="https://app.terraform.io/api/v2"
# 色設定
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# APIトークンチェック
if [[ -z "${TFE_TOKEN:-}" ]]; then
echo -e "${RED}エラー: TFE_TOKEN環境変数が設定されていません${NC}"
echo "HCP TerraformのAPIトークンを設定してください:"
echo "export TFE_TOKEN=your-api-token"
exit 1
fi
# 引数チェック
DRY_RUN=true
if [[ "${1:-}" == "--force" ]]; then
DRY_RUN=false
echo -e "${YELLOW}警告: --forceオプションが指定されました。実際に削除を実行します。${NC}"
elif [[ "${1:-}" == "--help" ]] || [[ "${1:-}" == "-h" ]]; then
echo "使用方法: $0 [--force]"
echo ""
echo "オプション:"
echo " --force 実際に削除を実行 (デフォルトはdry-run)"
echo " --help このヘルプを表示"
echo ""
echo "環境変数:"
echo " TFE_TOKEN HCP TerraformのAPIトークン (必須)"
echo " HCPTF_ORGANIZATION Organization名 (デフォルト: default-org)"
echo " HCPTF_PROJECT Project名 (デフォルト: default-project)"
exit 0
fi
if [[ "$DRY_RUN" == true ]]; then
echo -e "${BLUE}=== DRY RUNモード ===${NC}"
echo "削除対象のワークスペースを確認します(実際には削除されません)"
echo "実際に削除するには --force オプションを使用してください"
echo ""
fi
# API呼び出し関数
api_call() {
local endpoint="$1"
local method="${2:-GET}"
local data="${3:-}"
local curl_args=(
-s
-H "Authorization: Bearer ${TFE_TOKEN}"
-H "Content-Type: application/vnd.api+json"
-X "$method"
)
if [[ -n "$data" ]]; then
curl_args+=(-d "$data")
fi
curl "${curl_args[@]}" "${API_BASE_URL}${endpoint}"
}
# プロジェクトID取得(qパラメータで検索)
echo -e "${BLUE}プロジェクト情報を取得中...${NC}"
projects_response=$(api_call "/organizations/${ORGANIZATION}/projects?q=${PROJECT}")
project_id=$(echo "$projects_response" | jq -r --arg name "$PROJECT" '.data[] | select(.attributes.name == $name) | .id')
if [[ -z "$project_id" ]] || [[ "$project_id" == "null" ]]; then
echo -e "${RED}エラー: プロジェクト '${PROJECT}' が見つかりません${NC}"
echo "検索結果:"
echo "$projects_response" | jq -r '.data[] | " - \(.attributes.name) (ID: \(.id))"'
echo ""
echo "デバッグ情報:"
echo "検索で取得したプロジェクト数: $(echo "$projects_response" | jq '.data | length')"
echo "検索対象プロジェクト名: '${PROJECT}'"
exit 1
fi
echo -e "${GREEN}プロジェクトID: ${project_id}${NC}"
# ワークスペース一覧取得(プロジェクトでフィルタ、ページネーション対応)
echo -e "${BLUE}ワークスペース一覧を取得中...${NC}"
get_all_workspaces() {
local page=1
local all_data="[]"
while true; do
# URLエンコーディング: [=%5B, ]=%5D
# filter[project][id] → filter%5Bproject%5D%5Bid%5D
# page[number] → page%5Bnumber%5D, page[size] → page%5Bsize%5D
local response=$(api_call "/organizations/${ORGANIZATION}/workspaces?filter%5Bproject%5D%5Bid%5D=${project_id}&page%5Bnumber%5D=${page}&page%5Bsize%5D=100")
# APIエラーチェック
if ! echo "$response" | jq . > /dev/null 2>&1; then
echo -e "${RED}API呼び出しエラー (page $page): $response${NC}" >&2
break
fi
local current_data=$(echo "$response" | jq '.data')
local data_count=$(echo "$current_data" | jq 'length')
if [[ "$data_count" -eq 0 ]]; then
break
fi
# データをマージ
all_data=$(echo "$all_data" | jq ". + $current_data")
# 次のページがあるかチェック
local has_next=$(echo "$response" | jq -r '.meta.pagination."next-page" // empty')
if [[ -z "$has_next" ]] || [[ "$has_next" == "null" ]]; then
break
fi
((page++))
done
# 最終的なレスポンス形式を作成
echo "{\"data\": $all_data}"
}
workspaces_response=$(get_all_workspaces)
# リソース数0のワークスペースをフィルタ
empty_workspaces=$(echo "$workspaces_response" | jq -r '
.data[]
| select(.attributes["resource-count"] == 0)
| "\(.id)|\(.attributes.name)|\(.attributes["resource-count"])"
')
if [[ -z "$empty_workspaces" ]]; then
echo -e "${GREEN}リソース数0のワークスペースは見つかりませんでした${NC}"
exit 0
fi
echo -e "${YELLOW}削除対象のワークスペース:${NC}"
echo "$empty_workspaces" | while IFS='|' read -r ws_id ws_name resource_count; do
echo " - ${ws_name} (ID: ${ws_id}, リソース数: ${resource_count})"
done
workspace_count=$(echo "$empty_workspaces" | wc -l)
echo -e "${YELLOW}合計 ${workspace_count} 個のワークスペースが削除対象です${NC}"
if [[ "$DRY_RUN" == true ]]; then
echo ""
echo -e "${BLUE}=== DRY RUN完了 ===${NC}"
echo "実際に削除するには --force オプションを付けて実行してください:"
echo "$0 --force"
exit 0
fi
# 実際の削除確認
echo ""
echo -e "${RED}警告: 削除されたワークスペースは復元できません${NC}"
read -p "本当に削除しますか? [y/N]: " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "キャンセルされました"
exit 0
fi
# ワークスペース削除実行
echo -e "${BLUE}ワークスペース削除を開始...${NC}"
deleted_count=0
failed_count=0
# プロセス置換を使用してサブシェル問題を回避
while IFS='|' read -r ws_id ws_name resource_count; do
echo -n "削除中: ${ws_name} ... "
# Safe deleteを試行
delete_response=$(api_call "/workspaces/${ws_id}/actions/safe-delete" "POST" "" 2>&1)
delete_status=$?
if [[ $delete_status -eq 0 ]]; then
echo -e "${GREEN}成功${NC}"
((deleted_count++))
else
echo -e "${RED}失敗${NC}"
echo " エラー: $delete_response"
((failed_count++))
fi
done <<< "$empty_workspaces"
echo ""
echo -e "${BLUE}=== 削除完了 ===${NC}"
echo -e "${GREEN}成功: ${deleted_count} 個${NC}"
if [[ $failed_count -gt 0 ]]; then
echo -e "${RED}失敗: ${failed_count} 個${NC}"
fi
使い方
前提条件
以下のツールのインストールが必要です。
- curl
- jq
準備
スクリプトに実行権限を付与します。
chmod +x delete_empty_workspaces.sh
それぞれ環境変数をセットします。
export HCPTF_ORGANIZATION="your-organization"
export HCPTF_PROJECT="your-project"
export TFE_TOKEN="your-api-token"
TFE_TOKEN
はWorkspaceが削除が可能なトークンをセットしてください。
Macだったら、デフォルトで~/.terraform.d/credentials.tfrc.json
にトークンが格納されています。
cat ~/.terraform.d/credentials.tfrc.json
HCPTF_ORGANIZATION
とHCPTF_PROJECT
は環境変数で渡す以外にも、スクリプトでデフォルト値を設定できるようにしています。
デフォルト値を設定したい場合は、default-*
の部分を変更してください。
# 設定(環境変数または デフォルト値)
ORGANIZATION="${HCPTF_ORGANIZATION:-default-org}"
PROJECT="${HCPTF_PROJECT:-default-project}"
ドライランと実行
オプションなしで実際の削除を行わないドライランになります。
./delete_empty_workspaces.sh
--force
オプションをつけると実際の削除が行われます。
./delete_empty_workspaces.sh --force
動作確認
実際に空のWorkspaceをスクリプトを使って削除してみます。
検証用Workspaceは以下で作成しました。
terraform {
required_providers {
tfe = {
source = "hashicorp/tfe"
version = "0.68.2"
}
}
}
provider "tfe" {}
locals {
organization = "hoge" # 環境に合わせて修正
}
# Project for workspace deletion testing
resource "tfe_project" "delete_test" {
organization = local.organization
name = "sato-masaki-ws-delete-test"
description = "Project for testing workspace bulk deletion"
}
# Workspaces for deletion testing
resource "tfe_workspace" "test_1" {
name = "delete-test-1"
organization = local.organization
project_id = tfe_project.delete_test.id
description = "Test workspace 1 for bulk deletion"
}
resource "tfe_workspace" "test_2" {
name = "delete-test-2"
organization = local.organization
project_id = tfe_project.delete_test.id
description = "Test workspace 2 for bulk deletion"
}
resource "tfe_workspace" "test_3" {
name = "delete-test-3"
organization = local.organization
project_id = tfe_project.delete_test.id
description = "Test workspace 3 for bulk deletion"
}
テスト用のリソースを作成します。
terraform init
terraform apply
リソースを管理しているWorkspaceが削除対象にならないことを確認するために、delete-test-3
でリソースを作成しました。
まずはドライランです。
./delete_empty_workspaces.sh
=== DRY RUNモード ===
削除対象のワークスペースを確認します(実際には削除されません)
実際に削除するには --force オプションを使用してください
プロジェクト情報を取得中...
プロジェクトID: prj-XXXXX
ワークスペース一覧を取得中...
削除対象のワークスペース:
- delete-test-1 (ID: ws-XXXXX, リソース数: 0)
- delete-test-2 (ID: ws-XXXXX, リソース数: 0)
合計 2 個のワークスペースが削除対象です
=== DRY RUN完了 ===
実際に削除するには --force オプションを付けて実行してください:
./delete_empty_workspaces.sh --force
リソース数0のWorkspaceだけが削除対象になっていることを確認できました。
削除を実行します。
./delete_empty_workspaces.sh --force
./delete_empty_workspaces.sh --force
警告: --forceオプションが指定されました。実際に削除を実行します。
プロジェクト情報を取得中...
プロジェクトID: prj-XXXXX
ワークスペース一覧を取得中...
削除対象のワークスペース:
- delete-test-1 (ID: ws-XXXXXX, リソース数: 0)
- delete-test-2 (ID: ws-XXXXXX, リソース数: 0)
合計 2 個のワークスペースが削除対象です
警告: 削除されたワークスペースは復元できません
本当に削除しますか? [y/N]: y
ワークスペース削除を開始...
削除中: delete-test-1 ...成功
削除中: delete-test-2 ... 成功
=== 削除完了 ===
成功: 2 個
削除が完了しました。
HCP Terraformのコンソール上からも、リソース数0のWorkspaceだけが削除されたことを確認できました。
おわりに
使わなくなったWorkspaceの一括削除スクリプトについてでした。
検証用のWorkspaceをよく作るので、スクリプトで整理が楽になりました。
今回はとりあえずシェルスクリプトで実装してみました。
HashiCorpがメンテしているHCP TerraformのSDK go-tfeを使った実装も試してみたいと思います。