Created
April 17, 2025 13:09
-
-
Save rquadling/693bf50d98311c601fe9f80881478141 to your computer and use it in GitHub Desktop.
Generate terraform state manipulations for the Terraform Cloudflare Provider upgrade from v4 to v5.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# Check if arguments are provided (either tfvars files or default to terraform.tfvars) | |
TFVARS_FILES=("$@") # Collect arguments into an array | |
# If no arguments are provided, default to terraform.tfvars | |
if [ ${#TFVARS_FILES[@]} -eq 0 ]; then | |
TFVARS_FILES=("terraform.tfvars") | |
fi | |
# Check if the BACKEND_KEY_NAME environment variable is set | |
if [ -z "$BACKEND_KEY_NAME" ]; then | |
echo "Error: BACKEND_KEY_NAME environment variable is not set." | |
exit 1 | |
fi | |
# Initialize a variable to track the backend config | |
BACKEND_CONFIG="-backend-config key=${BACKEND_KEY_NAME}.tfstate" | |
# Inform the user which tfvars files are being used | |
echo "Using the following tfvars files: ${TFVARS_FILES[@]}" | |
# Ensure we're in the right repository directory (this assumes you've manually cd'd into the correct repo) | |
if [ ! -d ".git" ]; then | |
echo "Error: This script must be run from within a Git repository. Please ensure you're in the correct repo directory." | |
exit 1 | |
fi | |
# Inform the user which backend config is being used | |
echo "Using backend config: $BACKEND_CONFIG" | |
# Initialize Terraform for this repository (must be run to configure the remote backend) | |
echo "Running terraform init..." | |
terraform init -input=false $BACKEND_CONFIG -upgrade "${TFVARS_FILES[@]/#/--var-file=}" | |
# Fetch the current remote state and store it in a temporary file | |
STATE_FILE=$(mktemp) | |
terraform state pull > "$STATE_FILE" | |
# Run terraform plan with the provided tfvars files | |
echo "Running terraform plan..." | |
terraform plan "${TFVARS_FILES[@]/#/--var-file=}" | |
# Generate state removal and import commands for Cloudflare DNS records | |
echo "Generating state rm and import commands for Cloudflare DNS records..." | |
echo '' | |
echo '' > ../migrations.sh | |
chmod +x ../migrations.sh | |
# Define a search / replace map | |
declare -A RESOURCE_TYPE_MAP=( | |
["cloudflare_page_rule"]="cloudflare_page_rule" | |
["cloudflare_record"]="cloudflare_dns_record" | |
) | |
# Build a comma-separated list of quoted keys for the jq IN(...) call | |
JQ_TYPE_FILTER=$(printf '.type == "%s" or ' "${!RESOURCE_TYPE_MAP[@]}" | sed 's/ or $//') | |
# List the resources in the remote state | |
# Use jq to filter for cloudflare_record resources, preserving module and resource name | |
jq -crM '.resources[] | select('"$JQ_TYPE_FILTER"') | {module: .module, name: .name, type: .type, instances: .instances[]}' "$STATE_FILE" | while read -r line; do | |
# Extract module, resource name, zone_id, and RESOURCE_ID | |
MODULE_NAME=$(echo "$line" | jq -crM '.module') | |
RESOURCE_NAME=$(echo "$line" | jq -crM '.name') | |
RESOURCE_TYPE=$(echo "$line" | jq -crM '.type') | |
INDEX_KEY=$(echo "$line" | jq -crM '.instances.index_key') | |
ZONE_ID=$(echo "$line" | jq -crM '.instances.attributes.zone_id') | |
RESOURCE_ID=$(echo "$line" | jq -crM '.instances.attributes.id') | |
# If RESOURCE_ID are missing, skip this entry | |
if [[ -z "$RESOURCE_ID" ]]; then | |
echo "Error: Missing RESOURCE_ID for $line" | |
continue | |
fi | |
# Wrap index_key if it is present | |
if [[ -n "$INDEX_KEY" && "$INDEX_KEY" != "null" ]]; then | |
if [[ "$INDEX_KEY" =~ ^[0-9]+$ ]]; then | |
# Numeric index, no quotes | |
INDEX_KEY="[$INDEX_KEY]" | |
else | |
# String index, quoted | |
INDEX_KEY='["'"$INDEX_KEY"'"]' | |
fi | |
else | |
INDEX_KEY='' | |
fi | |
# Get the new type from the map | |
NEW_RESOURCE_TYPE="${RESOURCE_TYPE_MAP[$RESOURCE_TYPE]}" | |
if [[ -z "$NEW_RESOURCE_TYPE" ]]; then | |
echo "Error: No mapping found for resource type: $RESOURCE_TYPE" | |
continue | |
fi | |
# Determine if the resource is in a module or root module | |
if [ "$MODULE_NAME" == "root" ] || [ -z "$MODULE_NAME" ] || [ "$MODULE_NAME" == "null" ]; then | |
OLD_RESOURCE_PATH="$RESOURCE_TYPE.$RESOURCE_NAME" | |
NEW_RESOURCE_PATH="$NEW_RESOURCE_TYPE.$RESOURCE_NAME" | |
else | |
OLD_RESOURCE_PATH="$MODULE_NAME.$RESOURCE_TYPE.$RESOURCE_NAME" | |
NEW_RESOURCE_PATH="$MODULE_NAME.$NEW_RESOURCE_TYPE.$RESOURCE_NAME" | |
fi | |
# Generate the terraform state rm command with single quotes | |
echo "terraform state rm '$OLD_RESOURCE_PATH$INDEX_KEY'" >> ../migrations.sh | |
# Generate the terraform import command with correct zone_id/RESOURCE_ID format | |
if [[ -n "$ZONE_ID" && "$ZONE_ID" != "null" ]]; then | |
echo "terraform import "${TFVARS_FILES[@]/#/--var-file=}" '$NEW_RESOURCE_PATH$INDEX_KEY' '$ZONE_ID/$RESOURCE_ID'" >> ../migrations.sh | |
else | |
echo "terraform import "${TFVARS_FILES[@]/#/--var-file=}" '$NEW_RESOURCE_PATH$INDEX_KEY' '$RESOURCE_ID'" >> ../migrations.sh | |
fi | |
done | |
# Clean up the temporary state file | |
rm "$STATE_FILE" | |
echo '' | |
cat ../migrations.sh | |
echo '' | |
printf "Run \e[92m../migrations.sh\e[0m to run the above" | |
echo '' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment