2024-11-28
7 min read

How to Fix Terraform Import "Index Value Required" Error for String Keys

How to Fix Terraform Import "Index Value Required" Error for String Keys

When you try to import an existing resource into Terraform that was created with for_each, you might encounter an error like "Index value required" or "A reference to a resource type must be followed by at least one attribute access." This happens because Terraform needs to know which specific instance from the for_each map you're importing into.

Understanding how to properly reference for_each resources during import is essential for bringing existing infrastructure under Terraform management.

TLDR: When importing resources created with for_each, you must specify the key in square brackets with proper quoting: terraform import 'aws_instance.servers["web"]' i-1234567890abcdef0. The key must be quoted and the entire resource address should be wrapped in single quotes to prevent shell interpretation. For numeric keys, use resource["0"] syntax. The error occurs when you reference the resource without specifying which instance from the for_each map to import into.

Understanding the Error

When you have a resource created with for_each:

resource "aws_instance" "servers" {
  for_each = {
    web = {
      instance_type = "t3.medium"
    }
    api = {
      instance_type = "t3.large"
    }
  }

  ami           = var.ami_id
  instance_type = each.value.instance_type

  tags = {
    Name = each.key
  }
}

The resource aws_instance.servers is actually a map with keys "web" and "api". When importing, you can't just reference aws_instance.servers - you need to specify which key you're importing.

If you try:

# This will fail
terraform import aws_instance.servers i-1234567890abcdef0

You'll get an error like:

Error: Index value required

A reference to a resource type must be followed by at least one attribute access, specifying the resource name.

Correct Import Syntax for for_each Resources

The correct syntax uses square brackets to specify the key:

# Import the "web" instance
terraform import 'aws_instance.servers["web"]' i-1234567890abcdef0

# Import the "api" instance
terraform import 'aws_instance.servers["api"]' i-9876543210fedcba0

The single quotes around the resource address are important - they prevent your shell from interpreting the square brackets and quotes.

Different Shell Quoting Requirements

Different shells handle quoting differently:

Bash/Zsh (Linux/macOS):

# Single quotes - recommended
terraform import 'aws_instance.servers["web"]' i-1234567890abcdef0

# Double quotes with escaping
terraform import "aws_instance.servers[\"web\"]" i-1234567890abcdef0

PowerShell (Windows):

# Use backticks to escape quotes
terraform import "aws_instance.servers[\`"web\`"]" i-1234567890abcdef0

# Or single quotes for the whole thing
terraform import 'aws_instance.servers["web"]' i-1234567890abcdef0

Windows Command Prompt:

# Double quotes without escaping inner quotes works
terraform import "aws_instance.servers[\"web\"]" i-1234567890abcdef0

Importing Multiple for_each Instances

You need to import each instance separately:

# Import all instances from the for_each
terraform import 'aws_instance.servers["web"]' i-1234567890abcdef0
terraform import 'aws_instance.servers["api"]' i-9876543210fedcba0
terraform import 'aws_instance.servers["worker"]' i-abcdef1234567890

For many resources, create a script:

#!/bin/bash
# import-servers.sh

declare -A servers=(
  ["web"]="i-1234567890abcdef0"
  ["api"]="i-9876543210fedcba0"
  ["worker"]="i-abcdef1234567890"
)

for key in "${!servers[@]}"; do
  echo "Importing $key instance..."
  terraform import "aws_instance.servers[\"$key\"]" "${servers[$key]}"
done

Make it executable and run:

chmod +x import-servers.sh
./import-servers.sh

Numeric String Keys

If your for_each uses numeric string keys, you still need quotes:

resource "aws_subnet" "private" {
  for_each = {
    "0" = "10.0.1.0/24"
    "1" = "10.0.2.0/24"
    "2" = "10.0.3.0/24"
  }

  vpc_id     = aws_vpc.main.id
  cidr_block = each.value

  tags = {
    Name = "private-subnet-${each.key}"
  }
}

Import with quoted numeric keys:

terraform import 'aws_subnet.private["0"]' subnet-abc123
terraform import 'aws_subnet.private["1"]' subnet-def456
terraform import 'aws_subnet.private["2"]' subnet-ghi789

Finding the Correct Key to Use

If you're not sure what keys exist in your configuration, check your Terraform code:

# Look at the for_each definition
grep -A 10 "for_each" main.tf

# Or use terraform console
terraform console
> keys(aws_instance.servers)
["api", "web", "worker"]

For resources already in state:

# List resources in state
terraform state list

# You'll see output like:
# aws_instance.servers["web"]
# aws_instance.servers["api"]

Converting count to for_each Before Import

If you're migrating from count to for_each, you need to move resources in state:

# Old configuration with count
resource "aws_instance" "servers" {
  count = 3

  ami           = var.ami_id
  instance_type = "t3.medium"
}

# New configuration with for_each
resource "aws_instance" "servers" {
  for_each = {
    web    = { type = "t3.medium" }
    api    = { type = "t3.large" }
    worker = { type = "t3.small" }
  }

  ami           = var.ami_id
  instance_type = each.value.type
}

Move existing resources to match new keys:

# Move from count index to for_each key
terraform state mv 'aws_instance.servers[0]' 'aws_instance.servers["web"]'
terraform state mv 'aws_instance.servers[1]' 'aws_instance.servers["api"]'
terraform state mv 'aws_instance.servers[2]' 'aws_instance.servers["worker"]'

Import With Special Characters in Keys

If your keys contain special characters, they still need to be quoted:

resource "aws_s3_bucket" "apps" {
  for_each = {
    "my-app.example.com" = {
      versioning = true
    }
    "api-v2.example.com" = {
      versioning = false
    }
  }

  bucket = each.key

  versioning {
    enabled = each.value.versioning
  }
}

Import with special characters:

# Dots, hyphens, and other characters need quoting
terraform import 'aws_s3_bucket.apps["my-app.example.com"]' my-app.example.com
terraform import 'aws_s3_bucket.apps["api-v2.example.com"]' api-v2.example.com

Generating Import Statements Automatically

Create a script that generates import commands from existing AWS resources:

#!/bin/bash
# generate-imports.sh

# List all EC2 instances with a specific tag
aws ec2 describe-instances \
  --filters "Name=tag:ManagedBy,Values=terraform" \
  --query 'Reservations[*].Instances[*].[Tags[?Key==`Name`].Value|[0],InstanceId]' \
  --output text | \
while read name instance_id; do
  echo "terraform import 'aws_instance.servers[\"$name\"]' $instance_id"
done

This outputs import commands you can review and execute:

./generate-imports.sh
# Output:
# terraform import 'aws_instance.servers["web"]' i-1234567890abcdef0
# terraform import 'aws_instance.servers["api"]' i-9876543210fedcba0

Using import Blocks (Terraform 1.5+)

Starting with Terraform 1.5, you can use import blocks instead of the CLI:

# Define the import
import {
  to = aws_instance.servers["web"]
  id = "i-1234567890abcdef0"
}

import {
  to = aws_instance.servers["api"]
  id = "i-9876543210fedcba0"
}

# Your resource configuration
resource "aws_instance" "servers" {
  for_each = {
    web = { instance_type = "t3.medium" }
    api = { instance_type = "t3.large" }
  }

  ami           = var.ami_id
  instance_type = each.value.instance_type
}

Run terraform plan to see what will be imported:

terraform plan -generate-config-out=generated.tf

This generates the configuration for imported resources automatically.

Debugging Import Issues

If imports are still failing, verify the resource address:

# Check if the resource exists in your config
terraform providers schema -json | jq '.provider_schemas'

# Validate your configuration
terraform validate

# Check what Terraform expects
terraform plan

For complex scenarios, use terraform console to test expressions:

terraform console
> aws_instance.servers
{
  "api" = { ... }
  "web" = { ... }
}
> aws_instance.servers["web"]
{ ... instance details ... }

Common Mistakes

Forgetting quotes around the key:

# Wrong - shell interprets brackets
terraform import aws_instance.servers[web] i-1234567890abcdef0

# Correct
terraform import 'aws_instance.servers["web"]' i-1234567890abcdef0

Using the wrong quote style:

# Wrong - shell expands $key
terraform import "aws_instance.servers[$key]" i-1234567890abcdef0

# Correct
terraform import "aws_instance.servers[\"$key\"]" i-1234567890abcdef0

Not matching the for_each key exactly:

# Configuration uses lowercase
for_each = {
  web = { ... }
}
# Wrong - case doesn't match
terraform import 'aws_instance.servers["Web"]' i-1234567890abcdef0

# Correct - exact match
terraform import 'aws_instance.servers["web"]' i-1234567890abcdef0

Import Verification

After importing, verify it worked:

# Check state
terraform state show 'aws_instance.servers["web"]'

# Run a plan - should show no changes
terraform plan

# If there are changes, your config doesn't match the imported resource

If terraform plan shows changes after import, your configuration doesn't match the actual resource. Update your Terraform code to match the imported resource's current state.

Bulk Import Script

For importing many resources:

#!/bin/bash
# bulk-import.sh

# Read from a CSV file: key,resource_id
# Example: web,i-1234567890abcdef0

while IFS=',' read -r key resource_id; do
  echo "Importing $key ($resource_id)..."

  if terraform import "aws_instance.servers[\"$key\"]" "$resource_id"; then
    echo "✓ Successfully imported $key"
  else
    echo "✗ Failed to import $key"
  fi

  sleep 1  # Rate limiting
done < servers.csv

Create servers.csv:

web,i-1234567890abcdef0
api,i-9876543210fedcba0
worker,i-abcdef1234567890

Run the script:

chmod +x bulk-import.sh
./bulk-import.sh

The "Index value required" error when importing for_each resources is straightforward to fix once you understand that Terraform needs to know which specific instance you're importing. Always use square bracket notation with proper quoting, and remember that the key must exactly match what's in your for_each expression.

Published: 2024-11-28|Last updated: 2024-11-28T10:00:00Z

Found an issue?