Skip to content

Instantly share code, notes, and snippets.

@ruinshe
Last active December 24, 2025 01:07
Show Gist options
  • Select an option

  • Save ruinshe/a1f78e7c9726eb3031a4331c1f4be8b1 to your computer and use it in GitHub Desktop.

Select an option

Save ruinshe/a1f78e7c9726eb3031a4331c1f4be8b1 to your computer and use it in GitHub Desktop.
Terraform Kubernetes Provider v1 Migration Helper

Terraform Kubernetes Provider v1 Migration Helper

This script automates the tedious process of migrating Terraform state when upgrading HashiCorp's Kubernetes Provider resources from deprecated versions (e.g., kubernetes_secret) to their stable _v1 counterparts (e.g., kubernetes_secret_v1).

It is specifically designed to handle "hundreds of resources" where terraform state mv fails due to schema incompatibility or sheer volume.

Key Features

  • Module Aware: Correctly handles deeply nested modules (e.g., module.app.module.db.kubernetes_secret.pass).
  • JSON Parsing: Uses jq to parse Terraform state, avoiding regex fragility with HCL.
  • Auto-ID Discovery: Automatically extracts the correct Import ID (whether namespaced or cluster-scoped) from the existing state.
  • Safe: Generates a shell script for you to review before execution.

Prerequisites

  • jq installed.
  • Terraform initialized project.

Usage Workflow

  1. Update Code: Use sed or your IDE to find-and-replace all resource types in your .tf files (e.g., replace kubernetes_secret with kubernetes_secret_v1). Do not apply yet.
  2. Generate Script: Run this script (generate_k8s_v1_migration_full.sh) in your project root.
  3. Review: Check the generated migrate_with_modules.sh. It contains pairs of terraform state rm and terraform import commands.
  4. Backup State: (Highly Recommended)
    • Remote Backend: Run terraform state pull > tfstate_backup.json to create a backup.
    • Local Backend: Simply copy your state file: cp terraform.tfstate terraform.tfstate.backup.
  5. Execute: Run ./migrate_with_modules.sh to update your terraform.tfstate file without touching the actual cluster.
  6. Verify: Run terraform plan. You should see "No changes" (or minimal in-place updates due to default value changes).
#!/usr/bin/env bash
# ================= CONFIGURATION =================
# Target suffix for the new resources
SUFFIX="_v1"
# Output script filename
OUTPUT_SCRIPT="migrate_with_modules.sh"
# =============================================
# 0. Dependency Check
if ! command -v jq &> /dev/null; then
echo "❌ Error: 'jq' command not found."
echo "This script requires 'jq' to precisely parse Terraform State JSON."
echo "Please install it (e.g., nix-env -iA nixos.jq or apt install jq)"
exit 1
fi
echo "πŸš€ Starting Terraform State scan (JSON Parse Mode)..."
echo "Target: Migrate kubernetes_* to kubernetes_*${SUFFIX}"
# 1. Export State to JSON
# This is crucial for solving "ID not found" issues, as JSON always contains the 'id' field.
echo "πŸ“Š Exporting Terraform State to JSON..."
STATE_JSON="tf_state_dump.json"
terraform show -json > "$STATE_JSON"
if [ ! -s "$STATE_JSON" ]; then
echo "❌ Error: Failed to export State JSON, or the file is empty."
echo "Please ensure the current directory is initialized and has a valid state."
rm -f "$STATE_JSON"
exit 1
fi
# Initialize output script
echo "#!/usr/bin/env bash" > $OUTPUT_SCRIPT
echo "set -e" >> $OUTPUT_SCRIPT
count=0
skipped=0
# 2. Extract relevant resources using jq
# Logic explanation:
# 1. .values.root_module : Start from the root module.
# 2. recurse(.child_modules[]?) : Recursively traverse Terraform module structure (child_modules).
# 3. .resources[]? : Extract the resources list under each module.
# 4. select(...) : Filter resources, excluding kubernetes_manifest.
echo "πŸ” Analyzing JSON structure..."
# Use Process Substitution < <(...) instead of pipe |
# This ensures the while loop runs in the current shell, preserving variables like 'count' and making 'set -e' safer.
while IFS='|' read -r address type id; do
# 3. Filter Logic
# Check if the resource is already v1 (or v2)
if [[ "$type" == *"_v1"* ]] || [[ "$type" == *"_v2"* ]]; then
# Logic skip
continue
fi
# Check if ID is empty (Rare in JSON mode unless state is corrupted)
if [[ -z "$id" ]] || [[ "$id" == "null" ]]; then
echo "⚠️ Warning: ID for resource $address is empty (State might be incomplete). Skipping."
continue
fi
# 4. Construct new address
# 'address' example: module.foo.kubernetes_secret.bar
# 'type' example: kubernetes_secret
# We need to replace the 'type' segment in the address with 'type_v1'
new_type="${type}${SUFFIX}"
# Use sed for precise replacement: match ".type." or start of line "^type." to avoid replacing module names.
new_address=$(echo "$address" | sed -E "s/(^|\.)${type}\./\1${new_type}./")
echo "Processing: $address"
echo " -> Target: $new_address"
echo " -> ID: $id"
# 5. Write migration commands
echo "echo 'Migrating $address'" >> $OUTPUT_SCRIPT
echo "terraform state rm '$address'" >> $OUTPUT_SCRIPT
# Note: Quote the addresses and IDs to handle special characters (like those in kubernetes_manifest or complex IDs).
echo "terraform import '$new_address' '$id'" >> $OUTPUT_SCRIPT
echo "echo '-----------------------------------'" >> $OUTPUT_SCRIPT
((count++))
done < <(jq -r '
.values.root_module
| recurse(.child_modules[]?)
| .resources[]?
| select((.type | startswith("kubernetes_")) and (.type != "kubernetes_manifest"))
| "\(.address)|\(.type)|\(.values.id)"
' "$STATE_JSON")
# Clean up temporary JSON file
rm -f "$STATE_JSON"
chmod +x $OUTPUT_SCRIPT
echo "========================================"
echo "Scan Complete!"
echo "βœ… Resources to migrate: $count"
if [ $count -eq 0 ]; then
echo " (If 0 resources found, please check if migration was already done or if State is empty)"
fi
echo "Generated Migration Script: $OUTPUT_SCRIPT"
echo "PLEASE REVIEW the generated script content before executing!"
echo "========================================"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment