关于 tfctl
tfctl 是用于管理 HCP Terraform 的命令行技能,面向需要通过 API 查询和修改 Terraform 资源的用户。它强调内置 JSON 筛选、名称解析、敏感值脱敏和删除授权控制。
主要功能
- 01覆盖 HCP Terraform v2 API
- 02内置 gojq 筛选 JSON
- 03按名称解析资源
- 04默认脱敏敏感输出
- 05删除操作需会话授权
- 06支持变更预览
适用场景
- 01查询组织与工作区资源
- 02筛选并提取 API 响应
- 03预览变量等资源变更
- 04在授权会话中删除资源
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ hashicorp/ tfctl-cli --skill tfctl安装后在 Claude Code 中输入/tfctl确认已加载。
附带文件known_release_hashes
详细介绍
tfctl — HCP Terraform 命令行工具
单个二进制文件,完整覆盖 v2 API。已完成身份验证。
硬性规则
-
绝不要将
tfctl的 JSON 输出通过管道传给外部jq。 使用内置的--jq '<expr>'标志——它会自动启用--json,并在响应信封上运行 gojq。 -
使用
-p解析名称,不要单独发起查询。 路径中带有{workspace}/{team}/{project}/{varset}的命令支持-p workspace=NAME等参数——tfctl 会为你将名称解析为 ID。不要先获取 ID。 -
相信第一个答案。
data: []、data: null、relationships.X.data: null,或 stderr 中的 "no current run"/"not found" 就是答案。不要用其他格式重新查询。不要遍历关系来“验证”。 -
找不到指定名称的资源时,立即停止。 退出代码 2 或资源未出现在列表中就是完整答案。绝不要:
- 尝试其他资源 ID 来“验证端点是否可用”
- 转而使用可用列表中出现的其他组织/工作区
- 探索相关资源来寻找“类似”信息
- 用规则 3 为切换到其他资源辩解:如果你列出了组织,但其中没有 'platform',第一个答案就是“platform 不存在”——立即停止,不要改用列表中实际存在的组织。
示例:
run-POLICY返回退出代码 2 → 停止,不要查询其他运行 ID。组织列表中没有 'platform' → 停止,不要使用列表中实际存在的组织。 -
绝不要自行运行
tfctl harness exec来授权自己。--allow-delete授权必须由人类主动为你的会话选择。如果删除被拒绝,请将打印出的harness exec --allow-delete=<class>命令转告人类——不要运行该命令(也不要设置TFCTL_EXEC_SESSION)来授予自己权限。参见删除资源。
删除资源
删除具有破坏性,因此 tfctl 本身会对其进行限制——你不需要一概拒绝删除。只有人类通过 tfctl harness exec --allow-delete=<class> -- <command> 启动你,为当前会话主动授权后(此操作会设置 TFCTL_EXEC_SESSION),非交互式的 tfctl ... -X DELETE 才会执行。否则,tfctl 会自行拒绝,并告知你该怎么做。
因此,当有人要求你删除某项内容时,只需运行常规命令,让 tfctl 进行限制:
tfctl api PATH -X DELETE
- 如果会话已授权该资源类别,删除就会成功——这符合人类的意图,并不违规;继续操作并报告结果。
- 如果没有授权,
tfctl会拒绝,并打印一条说明性消息和需要转交的确切命令(包括人类可用于授权你的harness exec --allow-delete=<class>命令)。转告对方,不要强行执行;不要自行运行harness exec来授权自己。 - 你无法预先判断会话是否授予了某个类别,因此不要猜测。尝试删除,让拒绝信息告诉你结果。 拒绝时会返回普通的退出代码 1,并显示包含类别名称和
--allow-delete=<class>命令的信息;这就是你应当转告人类的提示。这是授权缺失,不是身份验证失败:除非tfctl明确表示令牌已过期或无效,否则绝不要将其报告为令牌过期、exit code 3,也不要告诉人类重新登录。 - 对不可逆的删除(
organizations、projects)保持常规谨慎。对于影响重大的资源,即使会话允许删除,也要先向人类确认意图。
URL 结构:每个工作区的子路径位于 /workspaces/{workspace}/...
大多数“关联到工作区的内容”(vars、runs、varsets、remote-state-consumers、configuration-versions、notification-configurations、state-versions)都位于 /workspaces/{workspace}/... 下,而非 /organizations/{org}/workspaces/{name}/...。组织嵌套形式仅适用于工作区资源本身(/organizations/{org}/workspaces/{name})。其他所有内容都使用 /workspaces/{workspace}/X -p workspace=NAME。
例外:策略检查和特定运行的数据位于 /runs/{run-id}/... 下,而不是工作区路径下。例如:/runs/{run-id}/policy-checks。
应避免的反模式
这些路径不存在;请勿尝试:
- ❌
/organizations/{org}/workspaces/{name}/vars— 请改用/workspaces/{workspace}/vars -p workspace=NAME - ❌
/organizations/{org}/workspaces/{name}/state-versions— 请改用/workspaces/{workspace}/state-versions -p workspace=NAME - ❌
/organizations/{org}/workspaces/{name}/configuration-versions— 请改用/workspaces/{workspace}/configuration-versions -p workspace=NAME - ❌
/workspaces/{workspace}/policy-checks— 请改用/runs/{run-id}/policy-checks - ❌
/organizations/{org}/varsets(路径部分错误)— 请使用/organizations/{organization}/varsets,并填写正确的组织占位符 - ❌ 外部
jq管道,例如tfctl ... | jq '...'— 始终使用内置标志tfctl api ... --jq '...'
速查手册 — 常见任务的一行答案
# 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)
密钥脱敏
默认情况下,tfctl 会对 api 命令输出中的敏感值进行脱敏,包括制品下载 URL、日志 URL、令牌、私有 SSH 密钥,以及一些未知内容,例如符合令牌启发式规则的变量值。--json 和 --jq 输出也会脱敏。提取所需密钥时,使用全局标志 --no-redact,即可针对单个请求停用脱敏。
输出标志
| 需求 | 标志 |
|---|---|
| 筛选 / 提取 | --jq '<expr>' |
| 完整 JSON | --json |
| 以便于阅读的格式呈现 | --markdown |
| 审核变更 | --dry-run |
| 不对密钥脱敏 | --no-redact |
--jq 会隐含启用 --json。不要同时传入两者。始终显式传入其中一个,不要依赖自动检测。
JSON:API 约定
响应采用 JSON:API 封套:{data: {id, type, attributes, relationships}}(列表则为 data: [...])。要跟随链接,请读取 data.relationships.<name>.data,其中包含 {id, type} 或 null。null 即为最终结果——不存在相关资源。
分页:默认第 1 页。添加 --all 获取所有页面(上限为 2000),或使用 --page-size N / --page-number N 显式控制。要获取数量,请使用 --page-size 1 --jq '.meta.pagination.["total-count"]'。
变更与批量更新
# 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":{…}}}'
添加 --dry-run 可预览而不发送请求。
退出代码(速查)
| 代码 | 含义 | 操作 |
|---|---|---|
| 0 | 成功 | 完成 |
| 1 | 提示信息或用法错误 | 阅读 stderr;如果提示“no current run”或类似内容,这就是答案——停止操作 |
| 2 | 未找到(工作区 / 运行不存在)或身份验证无效 | 确认 ID / 名称正确;如果仍然失败,再检查令牌 |
| 3 | 身份验证令牌已过期或无效 | 重新进行身份验证 |
| 4 | 网络错误 | 稍等片刻后重试 |
| 5 | 请求过多(429)或服务器错误(5xx) | 采用退避策略重试 |
| 6 | 资源处于错误状态 | 输出中已诊断错误(例如计划失败);请阅读错误信息 |
重要:当 API 返回 data: [](空列表)或 data: null 时,这就是答案。不要尝试使用不同的标志或端点重试。
常见故障排查
| 现象 | 原因 | 解决方法 |
|---|---|---|
列出工作区时出现 exit 2 | 组织不存在,或身份验证令牌无权访问 | 确认组织名称并重新进行身份验证 |
出现 exit 1 和“no current run” | 工作区当前没有活动运行(提示信息) | 这就是答案 — 停止操作,无需验证 |
进行任何 API 调用时出现 exit 3 | 身份验证令牌已过期 | 使用 tfctl login 重新进行身份验证 |
出现 exit 5(429 请求过多) | 请求过多 | 等待后重试;tfctl 会自动采用退避策略 |
出现 exit 6 和“plan is errored” | Terraform 计划存在语法错误(并非 CLI 错误) | 阅读计划输出以了解详情 |
筛选时列表为空(data: []) | 没有符合条件的资源 | 确认筛选条件正确;空列表是有效答案 |
智能默认值
- 如果已设置,
{organization}将从活动配置中解析。 {workspace}将从当前工作目录中的本地cloud {}块中解析。- 已构成的 ID(
ws-…、team-…、prj-…、varset-…)将按原样传递。