tfctl

作成者: hashicorp

tfctl CLIを使用してHCP Terraform / Terraform Cloud / Terraform Enterpriseと対話します。完全なAPIカバレッジ。任意のHCP TerraformまたはTerraform Cloudまたは…

npx skills add https://github.com/hashicorp/tfctl-cli --skill tfctl

tfctl — HCP Terraform CLI

Single binary, full v2 API coverage. Already authenticated.

Hard rules

  1. Never pipe tfctl JSON to an external jq. Use the built-in --jq '<expr>' flag — it implies --json and runs gojq on the response envelope.

  2. Resolve names with -p, not separate lookup calls. Paths with {workspace}/{team}/{project}/{varset} accept -p workspace=NAME etc. — tfctl resolves name→ID for you. Don't fetch the ID first.

  3. Trust the first answer. data: [], data: null, relationships.X.data: null, or stderr "no current run"/"not found" ARE the answer. Don't re-query in another format. Don't walk relationships "to verify".

  4. When a named resource is not found, stop completely. Exit code 2 or absence from a listing IS the full answer. Never:

    • Try a different resource ID "to verify the endpoint works"
    • Pivot to another org/workspace that appeared in the available list
    • Explore related resources to find "similar" information
    • Use Rule 3 to justify switching to a different resource: if you listed orgs and 'platform' isn't there, the first answer is "platform doesn't exist" — stop, don't use whatever org IS listed instead.

    Examples: run-POLICY returns exit 2 → stop, don't query other run IDs. Listing orgs shows no 'platform' → stop, don't use the org that IS listed.

  5. Never run tfctl harness exec yourself to self-authorize. The --allow-delete grant is a human's opt-in for your session. If a delete is refused, relay the printed harness exec --allow-delete=<class> command back to the human — do not run it (or set TFCTL_EXEC_SESSION) to grant yourself permission. See Deleting resources.

Deleting resources

Deletes are destructive, so tfctl itself gates them — you don't need to police this with a blanket refusal. A noninteractive tfctl ... -X DELETE only goes through when a human has opted in for this session by launching you via tfctl harness exec --allow-delete=<class> -- <command> (which sets TFCTL_EXEC_SESSION). Otherwise tfctl refuses on its own and tells you what to do.

So when you're asked to delete something, just run the normal command and let tfctl be the gate:

tfctl api PATH -X DELETE
  • If the session authorizes that resource's class, it succeeds — that's the human's intent, not a violation; proceed and report the result.
  • If it doesn't, tfctl refuses with a self-documenting message and prints the exact command to hand back (including the harness exec --allow-delete=<class> a human can use to authorize you). Relay that rather than forcing it; don't run harness exec yourself to self-authorize.
  • You can't tell in advance whether the session grants a class — so don't guess. Attempt the delete and let the refusal tell you. The refusal is a plain exit 1 with a message naming the class and the --allow-delete=<class> command; that is your cue to relay. This is a grant gap, NOT an auth failure: never report it as an expired token, an exit code 3, or tell the human to re-login unless tfctl actually says the token is expired/invalid.
  • Apply ordinary caution to irreversible deletes (organizations, projects). For a high-stakes resource, confirm intent with the human first even when the session would allow it.

URL shape: per-workspace subpaths live at /workspaces/{workspace}/...

Most "things attached to a workspace" (vars, runs, varsets, remote-state-consumers, configuration-versions, notification-configurations, state-versions) are under /workspaces/{workspace}/..., NOT /organizations/{org}/workspaces/{name}/.... The org-nested form only exists for the workspace resource itself (/organizations/{org}/workspaces/{name}). For everything else, use /workspaces/{workspace}/X -p workspace=NAME.

Exception: Policy checks and run-specific data live under /runs/{run-id}/..., not under workspace paths. For example: /runs/{run-id}/policy-checks.

Anti-patterns to avoid

These paths do not exist; don't try them:

  • /organizations/{org}/workspaces/{name}/vars — use /workspaces/{workspace}/vars -p workspace=NAME instead
  • /organizations/{org}/workspaces/{name}/state-versions — use /workspaces/{workspace}/state-versions -p workspace=NAME
  • /organizations/{org}/workspaces/{name}/configuration-versions — use /workspaces/{workspace}/configuration-versions -p workspace=NAME
  • /workspaces/{workspace}/policy-checks — use /runs/{run-id}/policy-checks instead
  • /organizations/{org}/varsets (partially wrong path) — use /organizations/{organization}/varsets with correct org placeholder
  • ❌ External jq pipes like tfctl ... | jq '...' — always use tfctl api ... --jq '...' with built-in flag

Cookbook — one-line answers for common tasks

# Count workspaces in an org
tfctl api /organizations/{organization}/workspaces --page-size 1 --jq '.meta.pagination.["total-count"]'

# Find workspace by partial name (server-side search) — also returns current run state in one call
tfctl api /organizations/{organization}/workspaces -f 'search[name]=TERM' --jq '.data[] | {id, name: .attributes.name, current_run: .relationships.["current-run"].data}'

# Filter workspaces by attribute
tfctl api /organizations/{organization}/workspaces --all --jq '.data[] | select(.attributes.["terraform-version"] | startswith("1.8")) | .attributes.name'

# List variables on a workspace — path is /workspaces/{workspace}/vars (NOT /organizations/{org}/workspaces/{name}/vars; that endpoint does not exist)
tfctl api /workspaces/{workspace}/vars -p workspace=NAME --jq '.data[] | {key: .attributes.key, category: .attributes.category, sensitive: .attributes.sensitive}'

# Get current run status
tfctl run status NAME_OR_ID            # If it prints "no current run" or exits non-zero with that message, that IS the answer.

# Get current run ID for a workspace
tfctl api /organizations/{organization}/workspaces/NAME --jq '.data.relationships.["current-run"].data.id'

# List runs in a workspace with status filtering
tfctl api /workspaces/{workspace}/runs -p workspace=NAME --jq '.data[] | select(.attributes.status == "planned") | {id, status: .attributes.status}'

# Find workspace by VCS repo identifier (single call; no results = not connected to that repo)
tfctl api /organizations/{organization}/workspaces --all \
  --jq '.data[] | select(.attributes.["vcs-repo"] != null and .attributes.["vcs-repo"].identifier == "org/repo") | {id: .id, name: .attributes.name}'

# List workspaces accessible to a team (two-step: resolve team name → query team-workspaces)
# Note: /organizations/{org}/teams/{id}/workspaces does NOT exist — use /team-workspaces instead
tfctl api /organizations/{organization}/teams -f 'filter[names]=TEAM_NAME' --jq '.data[0].id'
tfctl api /team-workspaces -f 'filter[team][id]=TEAM_ID' --jq '.data[] | {workspace_id: .relationships.workspace.data.id, access: .attributes.access}'

# Get organization details and settings
tfctl api /organizations/{organization} --jq '.data | {id, name: .attributes.name, created_at: .attributes."created-at", terraform_version_default: .attributes."terraform-version"}'

# Get the current state version for a workspace
# (operationId: getCurrentStateVersion — single resource, not a list)
tfctl api /workspaces/{workspace}/current-state-version -p workspace=NAME --jq '.data | {serial: .attributes.serial, created_at: .attributes.["created-at"], status: .attributes.status}'

# List all variable sets in an org and their variable counts
tfctl api /organizations/{organization}/varsets --all --jq '.data[] | {name: .attributes.name, id: .id, var_count: (.relationships.vars.data | length)}'

# Get configuration version details
tfctl api /workspaces/{workspace}/configuration-versions -p workspace=NAME --jq '.data[] | {id, source: .attributes.source, created_at: .attributes.created-at}'

# List notification configurations
tfctl api /workspaces/{workspace}/notification-configurations -p workspace=NAME --jq '.data[] | {id, type: .attributes.destination-type, trigger: .attributes.triggers}'

# Filter workspaces by terraform version (1.7+)
tfctl api /organizations/{organization}/workspaces --all --jq '.data[] | select(.attributes.["terraform-version"] | ltrimstr("v") | split(".") | [.[0], .[1]] | join(".") | tonumber >= 1.7) | {name: .attributes.name, tf_version: .attributes.["terraform-version"]}'

# Filter workspaces excluding certain names
tfctl api /organizations/{organization}/workspaces --all --jq '.data[] | select(.attributes.name | test("^temp-|^old-") | not) | .attributes.name'

# Count resources by status (e.g., runs by status)
tfctl api /workspaces/{workspace}/runs -p workspace=NAME --all --jq '[.data[] | .attributes.status] | group_by(.) | map({status: .[0], count: length})'

# Get log URL for a completed run
# Note: run.log-read-url is null on completed runs — the URL lives on plan/apply.
tfctl api /runs/RUN_ID --jq '.data.relationships | {plan: .plan.data.id, apply: .apply.data.id}'
tfctl api /plans/PLAN_ID --jq '.data.attributes.["log-read-url"]'

# Start a run
tfctl run start NAME_OR_ID

# Add a remote state consumer to a workspace
# (operationId: addWorkspaceRemoteStateConsumers — POST returns 204)
tfctl api /workspaces/{workspace}/relationships/remote-state-consumers -p workspace=NAME \
  -i '{"data":[{"type":"workspaces","id":"ws-CONSUMER_ID"}]}'

# Apply a variable set to a workspace
# (operationId: updateWorkspaceRelationship for varsets)
tfctl api /workspaces/{workspace}/relationships/varsets -p workspace=NAME \
  -X POST -i '{"data":[{"type":"varsets","id":"varset-VARSET_ID"}]}'

# Get policy check results for a run
tfctl api /runs/{run-id}/policy-checks --jq '.data[] | {id: .id, status: .attributes.status, enforced: .attributes.enforcement-level}'

# Discover an API operation when you don't know it
tfctl api schema search "KEYWORD" --json     # returns operationIds
tfctl api schema get OPERATION_ID            # full OpenAPI schema (large response — only call when needed)

Secret Redaction

By default, tfctl will redact the output of sensitive values from api command output, which includes artifact download URLs, log URLs, tokens, private SSH keys, and some unknown things such as variable values that match a token heuristic. This includes --json and --jq output. When extracting a secret that you need, use the --no-redact global flag to disable redaction for a single request.

Output flags

NeedFlag
Filter / extract--jq '<expr>'
Full JSON--json
Render for human--markdown
Audit a mutation--dry-run
Don't redact secrets--no-redact

--jq implies --json. Don't pass both. Always pass one explicitly — don't rely on auto-detect.

JSON:API conventions

Responses are JSON:API envelopes: {data: {id, type, attributes, relationships}} (or data: [...] for lists). To follow a link, read data.relationships.<name>.data which gives {id, type} or null. A null is final — there is no related resource.

Pagination: default page 1. Add --all for all pages (cap 2000), or --page-size N / --page-number N for explicit control. For counts, use --page-size 1 --jq '.meta.pagination.["total-count"]'.

Mutations & batch updates

# Create or update a single variable
tfctl api /workspaces/{workspace}/vars -p workspace=NAME -a key=VARKEY -a value=VALUE -a category=env

# Batch updates: use separate calls (HCP TFC doesn't support bulk POST for vars)
# Instead of trying /workspaces/{ws}/vars with multiple items, do:
tfctl api /workspaces/{workspace}/vars -p workspace=NAME -a key=VAR1 -a value=VAL1 -a category=env
tfctl api /workspaces/{workspace}/vars -p workspace=NAME -a key=VAR2 -a value=VAL2 -a category=env
# (Multiple calls cost more, but it's the supported pattern)

# PATCH with a raw body
tfctl api /workspaces/{workspace}/X -X PATCH -p workspace=NAME -i '{"data":{"type":"X","attributes":{…}}}'

Add --dry-run to preview without sending.

Exit codes (quick map)

CodeMeaningAction
0SuccessDone
1Informational message or usage errorRead stderr; if it says "no current run" or similar, that IS the answer — stop
2Not found (workspace/run doesn't exist) OR invalid authVerify the ID/name is correct, then check token if still failing
3Auth token expired or invalidRe-authenticate
4Network errorRetry after brief delay
5Rate limited (429) or server error (5xx)Retry with backoff
6Resource has an error stateThe error is already diagnosed in output (e.g., plan failed); read it

Important: When an API returns data: [] (empty list) or data: null, that IS the answer. Don't retry with different flags or endpoints.

Common troubleshooting

SymptomCauseFix
exit 2 when listing workspacesOrganization doesn't exist or auth token has no accessVerify org name and re-authenticate
exit 1 with "no current run"Workspace simply has no active run (informational)This is the answer — stop, don't verify
exit 3 when making any API callAuth token expiredRe-authenticate with tfctl login
exit 5 (429 rate limit)Too many requestsWait and retry; tfctl will backoff automatically
exit 6 with "plan is errored"Terraform plan had syntax errors (not CLI error)Read the plan output for details
Empty list (data: []) when filteringNo resources match criteriaVerify criteria is correct; empty list is valid answer

Smart defaults

  • {organization} resolves from active profile if set.
  • {workspace} resolves from a local cloud {} block in CWD.
  • Already-formed IDs (ws-…, team-…, prj-…, varset-…) are passed through as-is.

hashicorpのその他のスキル

provider-actions
hashicorp
Plugin Frameworkを使用してTerraform Providerのアクションを実装します。ライフサイクルイベント(前/後…)で実行される命令的操作を開発する際に使用します。
official
new-terraform-provider
hashicorp
新しいTerraformプロバイダーをPlugin Frameworkでスキャフォールディングする際に使用します:ワークスペースレイアウト、goモジュールのセットアップ、プロバイダーサーバーのmain.go、およびprovider.go…
official
terraform-test
hashicorp
Terraformテストの作成と実行に関する包括的なガイド。テストファイル(.tftest.hcl)の作成、runブロックを使ったテストシナリオの記述、検証の際に使用します…
official
terraform-test
hashicorp
Terraformテストの作成と実行に関する包括的なガイド。アサーション、モック、モジュール検証を含む。.tftest.hcl構文を使用してテストファイルを作成し、planまたはapplyモードで実行されるrunブロックを記述。逐次実行と並列実行をサポートし、オプションで状態の分離も可能。リソース属性、出力、データソースに対する条件をアサートし、expect_failuresを使用して無効な入力が適切に拒否されることを検証。モックプロバイダー(Terraform 1.7.0以降)はインフラストラクチャの動作をシミュレート...
official
provider-actions
hashicorp
プラグインフレームワークを使用して、リソースライフサイクルイベントで命令型のTerraformプロバイダーアクションを実装します。作成前/後および更新前/後のライフサイクルトリガーをサポートします(Terraform 1.14.0では破棄イベントは利用不可)。適切なスキーマ定義、正しいフレームワークタイプ、コレクション用のElementType、および入力検証用のバリデータが必要です。長時間実行操作のための進捗報告、タイムアウト管理、包括的なエラーハンドリングを含みます。ポーリングおよび...を実装します。
official
aws-ami-builder
hashicorp
Packerのamazon-ebsビルダーを使用してカスタムAmazonマシンイメージを構築します。ソースAMIからHCLテンプレートを使用してAMI作成を自動化し、プロビジョナー(シェルスクリプト、ファイルアップロード、構成管理)によるカスタマイズをサポートします。ami_regionsによるマルチリージョンAMI配布と、名前、所有者、仮想化タイプによる柔軟なソースAMIフィルタリングをサポートします。環境変数、AWS認証情報ファイル、またはIAMインスタンスプロファイルを介して認証し、テンプレートの検証およびビルドコマンドを含みます...
official
new-terraform-provider
hashicorp
Plugin Frameworkを使用して新しいTerraformプロバイダーをスキャフォールディングします。標準の「terraform-provider-」命名規則に従った新しいGoモジュールワークスペースを生成し、必要な依存関係を初期化します。カスタマイズ用のTODOマーカー付きで、HashiCorpのPlugin Frameworkパターンに従ったテンプレートmain.goファイルを提供します。ビルドおよびテストコマンドを実行してプロバイダーがコンパイルされ、初期チェックを通過することを確認することでセットアップを検証します。新しいワークスペースを作成する前に意図を確認することでワークスペース管理を処理します...
official
azure-verified-modules
hashicorp
AVM準拠を目指すAzure Terraformモジュールの認定要件とベストプラクティス。プロバイダーのバージョン制約(azurerm >= 4.0, < 5.0; azapi >= 2.0, < 3.0)を適用し、gitベースのモジュール参照を禁止して、固定されたTerraformレジストリソースを推奨。すべての識別子にスネークケース(小文字)を必須とし、正確な変数型、アンチコリューション層パターンによる個別の出力属性、アルファベット順のローカル変数を要求。新しいリソース追加には機能トグル変数を必要とする...
official