mcpField
T
tfctl

tfctl

github.com/hashicorp云与 DevOps6064 次安装5 周前更新
代码仓库

关于 tfctl

tfctl 是用于管理 HCP Terraform 的命令行技能,面向需要通过 API 查询和修改 Terraform 资源的用户。它强调内置 JSON 筛选、名称解析、敏感值脱敏和删除授权控制。

主要功能

  1. 01覆盖 HCP Terraform v2 API
  2. 02内置 gojq 筛选 JSON
  3. 03按名称解析资源
  4. 04默认脱敏敏感输出
  5. 05删除操作需会话授权
  6. 06支持变更预览

适用场景

  1. 01查询组织与工作区资源
  2. 02筛选并提取 API 响应
  3. 03预览变量等资源变更
  4. 04在授权会话中删除资源

安装

复制下面的命令,在终端中运行。

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

安装后在 Claude Code 中输入/tfctl确认已加载。

附带文件known_release_hashes

详细介绍

tfctl — HCP Terraform 命令行工具

单个二进制文件,完整覆盖 v2 API。已完成身份验证。

硬性规则

  1. 绝不要将 tfctl 的 JSON 输出通过管道传给外部 jq。 使用内置的 --jq '<expr>' 标志——它会自动启用 --json,并在响应信封上运行 gojq。

  2. 使用 -p 解析名称,不要单独发起查询。 路径中带有 {workspace}/{team}/{project}/{varset} 的命令支持 -p workspace=NAME 等参数——tfctl 会为你将名称解析为 ID。不要先获取 ID。

  3. 相信第一个答案。 data: []、data: null、relationships.X.data: null,或 stderr 中的 "no current run"/"not found" 就是答案。不要用其他格式重新查询。不要遍历关系来“验证”。

  4. 找不到指定名称的资源时,立即停止。 退出代码 2 或资源未出现在列表中就是完整答案。绝不要:

    • 尝试其他资源 ID 来“验证端点是否可用”
    • 转而使用可用列表中出现的其他组织/工作区
    • 探索相关资源来寻找“类似”信息
    • 用规则 3 为切换到其他资源辩解:如果你列出了组织,但其中没有 'platform',第一个答案就是“platform 不存在”——立即停止,不要改用列表中实际存在的组织。

    示例:run-POLICY 返回退出代码 2 → 停止,不要查询其他运行 ID。组织列表中没有 'platform' → 停止,不要使用列表中实际存在的组织。

  5. 绝不要自行运行 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-…)将按原样传递。

评论

支持 Markdown