mcpField
T
tfc-plan-json

tfc-plan-json

github.com/laurigatesCloud & DevOps5963 installsUpdated 1d ago
Repository

About tfc-plan-json

tfc-plan-json is a skill for downloading and analyzing Terraform Cloud run plan JSON, aimed at infrastructure teams reviewing planned changes. It summarizes resource actions and exposes details such as replacement reasons, provider versions, and output values.

Key features

  1. 01Download run plan JSON
  2. 02Summarize resource create, update, and delete actions
  3. 03Inspect resource replacement reasons
  4. 04Review provider versions and output values

Use cases

  1. 01Review planned resource changes
  2. 02Feed plan data to cost or policy tools
  3. 03Check why resources are replaced
  4. 04Verify provider versions used in a plan

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/laurigates/claude-plugins --skill tfc-plan-json

After installing, type/tfc-plan-jsonin Claude Code to confirm it loaded.

Details

From the project README

Terraform Cloud Plan JSON

Download and analyze structured plan JSON output from Terraform Cloud runs for detailed resource change analysis.

When to Use This Skill

Use this skill when...Use a sibling instead when...
Programmatically diffing which resources a plan will create/update/deleteReading the human-readable plan/apply log (tfc-run-logs)
Inspecting provider versions or planned output values in a runChecking only the pass/fail status of a run (tfc-run-status)
Auditing resource replacement reasons across a planListing multiple runs to find the right run ID (tfc-list-runs)
Feeding plan JSON into downstream tooling (cost, policy, drift)Browsing FVH-specific workspace runs by shorthand (tfc-workspace-runs)

Prerequisites

export TFE_TOKEN="your-api-token"        # User or team token with admin workspace access
export TFE_ADDRESS="app.terraform.io"    # Optional

Core Commands

Download Plan JSON
#!/bin/bash
set -euo pipefail

TOKEN="${TFE_TOKEN:?TFE_TOKEN not set}"
BASE_URL="https://${TFE_ADDRESS:-app.terraform.io}/api/v2"
RUN_ID="${1:?Usage: $0 <run-id> [output-file]}"
OUTPUT="${2:-plan.json}"

# Download with redirect following (API returns 307)
curl -Lsf --header "Authorization: Bearer $TOKEN" \
  -o "$OUTPUT" \
  "$BASE_URL/runs/$RUN_ID/plan/json-output"

echo "Plan JSON saved to: $OUTPUT"
Download via Plan ID
TOKEN="${TFE_TOKEN:?TFE_TOKEN not set}"
PLAN_ID="plan-xyz789"

curl -Lsf --header "Authorization: Bearer $TOKEN" \
  -o plan.json \
  "https://app.terraform.io/api/v2/plans/$PLAN_ID/json-output"

Analysis Commands

Resource Change Summary
curl -Lsf --header "Authorization: Bearer $TFE_TOKEN" \
  "https://app.terraform.io/api/v2/runs/$RUN_ID/plan/json-output" | \
  jq '{
    terraform_version: .terraform_version,
    format_version: .format_version,
    summary: {
      create: [.resource_changes[] | select(.change.actions | contains(["create"]))] | length,
      update: [.resource_changes[] | select(.change.actions | contains(["update"]))] | length,
      delete: [.resource_changes[] | select(.change.actions | contains(["delete"]))] | length,
      replace: [.resource_changes[] | select(.change.actions | contains(["delete", "create"]))] | length,
      read: [.resource_changes[] | select(.change.actions | contains(["read"]))] | length,
      no_op: [.resource_changes[] | select(.change.actions == ["no-op"])] | length
    }
  }'
List Resources Being Created
curl -Lsf --header "Authorization: Bearer $TFE_TOKEN" \
  "https://app.terraform.io/api/v2/runs/$RUN_ID/plan/json-output" | \
  jq -r '.resource_changes[] | select(.change.actions | contains(["create"])) | .address'
List Resources Being Destroyed
curl -Lsf --header "Authorization: Bearer $TFE_TOKEN" \
  "https://app.terraform.io/api/v2/runs/$RUN_ID/plan/json-output" | \
  jq -r '.resource_changes[] | select(.change.actions | contains(["delete"])) | .address'
List Resources Being Updated
curl -Lsf --header "Authorization: Bearer $TFE_TOKEN" \
  "https://app.terraform.io/api/v2/runs/$RUN_ID/plan/json-output" | \
  jq -r '.resource_changes[] | select(.change.actions | contains(["update"])) | .address'
Resources Being Replaced
curl -Lsf --header "Authorization: Bearer $TFE_TOKEN" \
  "https://app.terraform.io/api/v2/runs/$RUN_ID/plan/json-output" | \
  jq -r '.resource_changes[] | select(.change.actions | contains(["delete", "create"])) |
    "\(.address) (replace due to: \(.action_reason // "unknown"))"'
Detailed Resource Changes
curl -Lsf --header "Authorization: Bearer $TFE_TOKEN" \
  "https://app.terraform.io/api/v2/runs/$RUN_ID/plan/json-output" | \
  jq '.resource_changes[] | select(.change.actions != ["no-op"]) | {
    address: .address,
    actions: .change.actions,
    before: .change.before,
    after: .change.after
  }'
Show What's Changing in a Specific Resource
RESOURCE="aws_instance.web"

curl -Lsf --header "Authorization: Bearer $TFE_TOKEN" \
  "https://app.terraform.io/api/v2/runs/$RUN_ID/plan/json-output" | \
  jq --arg addr "$RESOURCE" '
    .resource_changes[] | select(.address == $addr) | {
      address: .address,
      actions: .change.actions,
      before: .change.before,
      after: .change.after,
      after_unknown: .change.after_unknown
    }'
Provider Versions Used
curl -Lsf --header "Authorization: Bearer $TFE_TOKEN" \
  "https://app.terraform.io/api/v2/runs/$RUN_ID/plan/json-output" | \
  jq '.configuration.provider_config | to_entries | map({
    provider: .key,
    version: .value.version_constraint
  })'
Output Values
curl -Lsf --header "Authorization: Bearer $TFE_TOKEN" \
  "https://app.terraform.io/api/v2/runs/$RUN_ID/plan/json-output" | \
  jq '.output_changes | to_entries | map({
    name: .key,
    actions: .value.actions,
    sensitive: .value.after_sensitive
  })'
Variables Used
curl -Lsf --header "Authorization: Bearer $TFE_TOKEN" \
  "https://app.terraform.io/api/v2/runs/$RUN_ID/plan/json-output" | \
  jq '.variables | keys'

Complete Analysis Script

#!/bin/bash
set -euo pipefail

TOKEN="${TFE_TOKEN:?TFE_TOKEN not set}"
RUN_ID="${1:?Usage: $0 <run-id>}"

PLAN=$(curl -Lsf --header "Authorization: Bearer $TOKEN" \
  "https://app.terraform.io/api/v2/runs/$RUN_ID/plan/json-output")

echo "=== Plan Analysis for $RUN_ID ==="
echo ""
echo "Terraform Version: $(echo "$PLAN" | jq -r '.terraform_version')"
echo ""

echo "Resource Changes:"
echo "  Create:  $(echo "$PLAN" | jq '[.resource_changes[] | select(.change.actions | contains(["create"]))] | length')"
echo "  Update:  $(echo "$PLAN" | jq '[.resource_changes[] | select(.change.actions | contains(["update"]))] | length')"
echo "  Delete:  $(echo "$PLAN" | jq '[.resource_changes[] | select(.change.actions | contains(["delete"]))] | length')"
echo "  Replace: $(echo "$PLAN" | jq '[.resource_changes[] | select(.change.actions | contains(["delete", "create"]))] | length')"
echo ""

echo "Resources to Create:"
echo "$PLAN" | jq -r '.resource_changes[] | select(.change.actions | contains(["create"])) | "  - " + .address'

echo ""
echo "Resources to Destroy:"
echo "$PLAN" | jq -r '.resource_changes[] | select(.change.actions | contains(["delete"])) | "  - " + .address'

echo ""
echo "Resources to Update:"
echo "$PLAN" | jq -r '.resource_changes[] | select(.change.actions | contains(["update"])) | "  - " + .address'

Plan JSON Structure

The plan JSON output follows Terraform's JSON plan format:

{
  "format_version": "1.2",
  "terraform_version": "1.5.0",
  "planned_values": { ... },
  "resource_changes": [
    {
      "address": "aws_instance.web",
      "mode": "managed",
      "type": "aws_instance",
      "name": "web",
      "provider_name": "registry.terraform.io/hashicorp/aws",
      "change": {
        "actions": ["create"],
        "before": null,
        "after": { ... },
        "after_unknown": { ... },
        "before_sensitive": false,
        "after_sensitive": { ... }
      }
    }
  ],
  "output_changes": { ... },
  "configuration": { ... },
  "variables": { ... }
}
Change Actions
  • ["create"] - Resource will be created
  • ["delete"] - Resource will be destroyed
  • ["update"] - Resource will be updated in-place
  • ["delete", "create"] - Resource will be replaced
  • ["read"] - Data source will be read
  • ["no-op"] - No changes

Important Notes

  • Requires Terraform 0.12+ for JSON output support
  • Returns 204 No Content if plan hasn't completed yet
  • Follow redirects - API returns HTTP 307 to temporary download URL
  • Temporary URL - Download URL is valid for ~1 minute
  • Admin access required - Need admin permissions on the workspace

Error Handling

204 No Content

Plan hasn't completed yet. Check run status first.

401 Unauthorized

Token lacks admin workspace access or is invalid.

404 Not Found

Run doesn't exist or you don't have permission.

See Also

  • tfc-run-logs: Get plan/apply logs (human-readable)
  • tfc-run-status: Quick status check for a run
  • tfc-list-runs: List recent runs in a workspace

Comments

Markdown supported