home / skills / laurigates / claude-plugins / tfc-plan-json

This skill downloads and analyzes Terraform Cloud plan JSON to summarize changes, enabling quick insight into resource updates and replacements.

npx playbooks add skill laurigates/claude-plugins --skill tfc-plan-json

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
7.8 KB
---
model: haiku
created: 2025-12-16
modified: 2025-12-16
reviewed: 2025-12-16
name: tfc-plan-json
description: Download and analyze structured Terraform plan JSON output from Terraform Cloud. Use when analyzing resource changes, diffing infrastructure, or programmatically inspecting plan details. Requires TFE_TOKEN environment variable.
allowed-tools: Bash, Read
---

# Terraform Cloud Plan JSON

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

## Prerequisites

```bash
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

```bash
#!/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

```bash
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

```bash
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

```bash
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

```bash
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

```bash
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

```bash
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

```bash
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

```bash
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

```bash
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

```bash
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

```bash
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

```bash
#!/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:

```json
{
  "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

Overview

This skill downloads and analyzes Terraform Cloud plan JSON output so you can inspect planned resource changes programmatically. It provides safe, scripted downloads (handling Terraform Cloud redirects) and jq-based analysis snippets to summarize creates, updates, deletes, replacements, provider versions, outputs, and variables. Requires a TFE_TOKEN environment variable with workspace admin access.

How this skill works

The skill calls Terraform Cloud API endpoints for a run or plan to retrieve the plan JSON, following the temporary redirect to the real download URL. Once retrieved it uses jq or simple shell scripts to extract the terraform_version, resource_changes, output_changes, provider configuration, and variables, and then produces concise summaries and lists of affected resources. It handles common HTTP responses like 204, 401, and 404 and documents required permissions and token usage.

When to use it

  • Before applying changes to review exactly which resources will be created, updated, destroyed, or replaced.
  • When automating CI/CD policies that must programmatically gate or approve Terraform runs.
  • To generate reports that list resource addresses and reasons for replacement.
  • When debugging unexpected drift or change plans by inspecting before/after values.
  • To extract provider versions, outputs, or variables consumed by downstream tooling.

Best practices

  • Set TFE_TOKEN in CI with least privilege required; admin workspace access is needed to download plan JSON.
  • Check run status before requesting plan JSON to avoid 204 No Content responses.
  • Follow redirects and use short-lived downloads immediately — Terraform Cloud download URLs expire quickly.
  • Use jq filters to limit output to the fields you need to reduce noise and avoid leaking sensitive values.
  • Store plan JSON temporarily in secure CI artifacts and delete after analysis if it contains sensitive data.

Example use cases

  • CI pipeline step that fails a PR if the plan includes any resource deletions or replacements.
  • Automated audit that counts create/update/delete actions across recent runs for reporting.
  • Script that extracts changed resource addresses to trigger targeted downstream tests.
  • Quick investigator script to show before/after values for a specific resource address.
  • Gather provider version constraints from a run to verify compliance with approved providers.

FAQ

What environment variables are required?

You must set TFE_TOKEN with a token that has admin access to the workspace. Optionally set TFE_ADDRESS to target a private TFC instance.

Why do I get 204 No Content?

A 204 indicates the plan for the run is not yet available. Verify the run finished planning and try again.

How long is the download URL valid?

The API returns a temporary redirect; the download URL is typically valid for about one minute, so fetch immediately.