Tutorial 24: Resource Targeting and Partial Application
Learning Objectives
By the end of this tutorial, you will be able to:
- Use resource targeting to apply changes to specific resources
- Implement partial application strategies for large infrastructures
- Apply selective updates and rollbacks using targeting
- Design safe deployment patterns with targeted operations
- Troubleshoot and debug using resource targeting techniques
Prerequisites
- Understanding of Terraform state management
- Completed Tutorial 13: Manage Resource Targeting
- Knowledge of Terraform CLI operations
Introduction
Resource targeting allows you to focus Terraform operations on specific resources or modules, enabling precise control over infrastructure changes. This capability is essential for large, complex infrastructures where you need to apply changes incrementally or troubleshoot specific components.
Basic Resource Targeting
Single Resource Targeting
# main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.project_name}-vpc"
}
}
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + 1)
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-public-subnet-${count.index + 1}"
Type = "public"
}
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-igw"
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "${var.project_name}-public-rt"
}
}
resource "aws_route_table_association" "public" {
count = length(aws_subnet.public)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_security_group" "web" {
name = "${var.project_name}-web-sg"
description = "Security group for web servers"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTP"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-web-sg"
}
}
resource "aws_instance" "web" {
count = var.instance_count
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
subnet_id = aws_subnet.public[count.index % length(aws_subnet.public)].id
vpc_security_group_ids = [aws_security_group.web.id]
user_data = templatefile("${path.module}/user_data.sh", {
server_name = "${var.project_name}-web-${count.index + 1}"
})
tags = {
Name = "${var.project_name}-web-${count.index + 1}"
Type = "web-server"
}
}
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
}
Targeting Examples
# Target a specific resource
terraform plan -target=aws_instance.web[0]
terraform apply -target=aws_instance.web[0]
# Target multiple specific resources
terraform plan -target=aws_instance.web[0] -target=aws_instance.web[1]
terraform apply -target=aws_instance.web[0] -target=aws_instance.web[1]
# Target all instances of a resource type
terraform plan -target=aws_instance.web
terraform apply -target=aws_instance.web
# Target a specific security group
terraform plan -target=aws_security_group.web
terraform apply -target=aws_security_group.web
# Target network infrastructure
terraform plan -target=aws_vpc.main -target=aws_subnet.public -target=aws_internet_gateway.main
terraform apply -target=aws_vpc.main -target=aws_subnet.public -target=aws_internet_gateway.main
Module Targeting
Multi-Module Infrastructure
# modules/vpc/main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = var.enable_dns_hostnames
enable_dns_support = var.enable_dns_support
tags = merge(var.common_tags, {
Name = "${var.project_name}-vpc"
})
}
resource "aws_subnet" "public" {
count = length(var.public_subnets)
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnets[count.index]
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = merge(var.common_tags, {
Name = "${var.project_name}-public-${count.index + 1}"
Type = "public"
})
}
resource "aws_subnet" "private" {
count = length(var.private_subnets)
vpc_id = aws_vpc.main.id
cidr_block = var.private_subnets[count.index]
availability_zone = var.availability_zones[count.index]
tags = merge(var.common_tags, {
Name = "${var.project_name}-private-${count.index + 1}"
Type = "private"
})
}
# modules/vpc/outputs.tf
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "IDs of the public subnets"
value = aws_subnet.public[*].id
}
output "private_subnet_ids" {
description = "IDs of the private subnets"
value = aws_subnet.private[*].id
}
# modules/compute/main.tf
resource "aws_launch_template" "app" {
name_prefix = "${var.project_name}-app-"
image_id = var.ami_id
instance_type = var.instance_type
vpc_security_group_ids = [var.security_group_id]
user_data = base64encode(templatefile("${path.module}/user_data.sh", {
app_name = var.project_name
environment = var.environment
}))
tag_specifications {
resource_type = "instance"
tags = merge(var.common_tags, {
Name = "${var.project_name}-app-instance"
})
}
}
resource "aws_autoscaling_group" "app" {
name = "${var.project_name}-asg"
vpc_zone_identifier = var.subnet_ids
target_group_arns = [var.target_group_arn]
health_check_type = "ELB"
min_size = var.min_instances
max_size = var.max_instances
desired_capacity = var.desired_instances
launch_template {
id = aws_launch_template.app.id
version = "$Latest"
}
tag {
key = "Name"
value = "${var.project_name}-asg"
propagate_at_launch = false
}
tag {
key = "Environment"
value = var.environment
propagate_at_launch = true
}
}
# modules/database/main.tf
resource "aws_db_subnet_group" "main" {
name = "${var.project_name}-db-subnet-group"
subnet_ids = var.subnet_ids
tags = merge(var.common_tags, {
Name = "${var.project_name}-db-subnet-group"
})
}
resource "aws_db_instance" "main" {
identifier = "${var.project_name}-database"
engine = var.engine
engine_version = var.engine_version
instance_class = var.instance_class
allocated_storage = var.allocated_storage
storage_encrypted = var.storage_encrypted
db_name = var.db_name
username = var.username
password = var.password
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [var.security_group_id]
backup_retention_period = var.backup_retention_period
backup_window = var.backup_window
maintenance_window = var.maintenance_window
skip_final_snapshot = var.skip_final_snapshot
tags = merge(var.common_tags, {
Name = "${var.project_name}-database"
})
}
# Root main.tf
module "vpc" {
source = "./modules/vpc"
project_name = var.project_name
vpc_cidr = var.vpc_cidr
public_subnets = var.public_subnets
private_subnets = var.private_subnets
availability_zones = var.availability_zones
enable_dns_hostnames = true
enable_dns_support = true
common_tags = local.common_tags
}
module "security" {
source = "./modules/security"
project_name = var.project_name
vpc_id = module.vpc.vpc_id
vpc_cidr = var.vpc_cidr
common_tags = local.common_tags
}
module "load_balancer" {
source = "./modules/load_balancer"
project_name = var.project_name
vpc_id = module.vpc.vpc_id
public_subnet_ids = module.vpc.public_subnet_ids
security_group_id = module.security.alb_security_group_id
common_tags = local.common_tags
}
module "compute" {
source = "./modules/compute"
project_name = var.project_name
environment = var.environment
ami_id = data.aws_ami.ubuntu.id
instance_type = var.instance_type
subnet_ids = module.vpc.private_subnet_ids
security_group_id = module.security.app_security_group_id
target_group_arn = module.load_balancer.target_group_arn
min_instances = var.min_instances
max_instances = var.max_instances
desired_instances = var.desired_instances
common_tags = local.common_tags
}
module "database" {
source = "./modules/database"
project_name = var.project_name
engine = "mysql"
engine_version = "8.0"
instance_class = var.db_instance_class
allocated_storage = var.db_allocated_storage
storage_encrypted = true
db_name = var.db_name
username = var.db_username
password = var.db_password
subnet_ids = module.vpc.private_subnet_ids
security_group_id = module.security.db_security_group_id
backup_retention_period = var.db_backup_retention
backup_window = "03:00-04:00"
maintenance_window = "sun:04:00-sun:05:00"
skip_final_snapshot = var.environment != "prod"
common_tags = local.common_tags
}
locals {
common_tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "terraform"
CreatedAt = formatdate("YYYY-MM-DD", timestamp())
}
}
Module Targeting Examples
# Target entire VPC module
terraform plan -target=module.vpc
terraform apply -target=module.vpc
# Target specific resources within a module
terraform plan -target=module.vpc.aws_vpc.main
terraform apply -target=module.vpc.aws_vpc.main
# Target multiple modules
terraform plan -target=module.vpc -target=module.security
terraform apply -target=module.vpc -target=module.security
# Target compute infrastructure only
terraform plan -target=module.compute -target=module.load_balancer
terraform apply -target=module.compute -target=module.load_balancer
# Target database module
terraform plan -target=module.database
terraform apply -target=module.database
# Target all network-related modules
terraform plan -target=module.vpc -target=module.security -target=module.load_balancer
terraform apply -target=module.vpc -target=module.security -target=module.load_balancer
Advanced Targeting Patterns
Environment-Specific Targeting
# environments/dev/main.tf
module "infrastructure" {
source = "../../modules/infrastructure"
project_name = var.project_name
environment = "dev"
# Development-specific configurations
instance_count = 1
instance_type = "t3.micro"
enable_monitoring = false
enable_backups = false
vpc_cidr = "10.0.0.0/16"
tags = {
Environment = "dev"
Cost-Center = "engineering"
}
}
# environments/staging/main.tf
module "infrastructure" {
source = "../../modules/infrastructure"
project_name = var.project_name
environment = "staging"
# Staging-specific configurations
instance_count = 2
instance_type = "t3.small"
enable_monitoring = true
enable_backups = true
vpc_cidr = "10.1.0.0/16"
tags = {
Environment = "staging"
Cost-Center = "engineering"
}
}
# environments/prod/main.tf
module "infrastructure" {
source = "../../modules/infrastructure"
project_name = var.project_name
environment = "prod"
# Production-specific configurations
instance_count = 5
instance_type = "t3.medium"
enable_monitoring = true
enable_backups = true
enable_multi_az = true
vpc_cidr = "10.2.0.0/16"
tags = {
Environment = "prod"
Cost-Center = "engineering"
Critical = "true"
}
}
Deployment Pipeline Targeting
#!/bin/bash
# deploy.sh - Deployment script with targeting
set -e
ENVIRONMENT=${1:-dev}
TARGET=${2:-all}
ACTION=${3:-apply}
echo "Deploying to $ENVIRONMENT with target: $TARGET"
cd "environments/$ENVIRONMENT"
case $TARGET in
"network")
echo "Deploying network infrastructure..."
terraform $ACTION -target=module.infrastructure.module.vpc \
-target=module.infrastructure.module.security \
-auto-approve
;;
"compute")
echo "Deploying compute infrastructure..."
terraform $ACTION -target=module.infrastructure.module.compute \
-target=module.infrastructure.module.load_balancer \
-auto-approve
;;
"database")
echo "Deploying database infrastructure..."
terraform $ACTION -target=module.infrastructure.module.database \
-auto-approve
;;
"monitoring")
echo "Deploying monitoring infrastructure..."
terraform $ACTION -target=module.infrastructure.module.monitoring \
-auto-approve
;;
"all")
echo "Deploying all infrastructure..."
terraform $ACTION -auto-approve
;;
*)
echo "Unknown target: $TARGET"
echo "Available targets: network, compute, database, monitoring, all"
exit 1
;;
esac
echo "Deployment completed successfully!"
Blue-Green Deployment with Targeting
# variables.tf
variable "deployment_color" {
description = "Current deployment color (blue or green)"
type = string
default = "blue"
validation {
condition = contains(["blue", "green"], var.deployment_color)
error_message = "Deployment color must be either 'blue' or 'green'."
}
}
variable "traffic_weight" {
description = "Traffic weight for current deployment (0-100)"
type = number
default = 100
validation {
condition = var.traffic_weight >= 0 && var.traffic_weight <= 100
error_message = "Traffic weight must be between 0 and 100."
}
}
# main.tf
locals {
current_color = var.deployment_color
inactive_color = var.deployment_color == "blue" ? "green" : "blue"
}
# Blue deployment
module "blue_deployment" {
source = "./modules/deployment"
project_name = var.project_name
environment = var.environment
color = "blue"
instance_count = var.deployment_color == "blue" ? var.instance_count : 0
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
security_group_id = module.security.app_security_group_id
ami_id = var.blue_ami_id
instance_type = var.instance_type
tags = merge(local.common_tags, {
Color = "blue"
})
}
# Green deployment
module "green_deployment" {
source = "./modules/deployment"
project_name = var.project_name
environment = var.environment
color = "green"
instance_count = var.deployment_color == "green" ? var.instance_count : 0
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
security_group_id = module.security.app_security_group_id
ami_id = var.green_ami_id
instance_type = var.instance_type
tags = merge(local.common_tags, {
Color = "green"
})
}
# Load balancer with weighted routing
resource "aws_lb_listener_rule" "blue" {
listener_arn = aws_lb_listener.app.arn
priority = 100
action {
type = "forward"
target_group_arn = module.blue_deployment.target_group_arn
forward {
target_group {
arn = module.blue_deployment.target_group_arn
weight = var.deployment_color == "blue" ? var.traffic_weight : (100 - var.traffic_weight)
}
target_group {
arn = module.green_deployment.target_group_arn
weight = var.deployment_color == "green" ? var.traffic_weight : (100 - var.traffic_weight)
}
}
}
condition {
path_pattern {
values = ["/*"]
}
}
}
Blue-Green Deployment Scripts
#!/bin/bash
# blue-green-deploy.sh
set -e
CURRENT_COLOR=${1:-blue}
NEW_AMI_ID=${2}
ENVIRONMENT=${3:-staging}
if [ -z "$NEW_AMI_ID" ]; then
echo "Usage: $0 <current_color> <new_ami_id> [environment]"
exit 1
fi
INACTIVE_COLOR=$([ "$CURRENT_COLOR" = "blue" ] && echo "green" || echo "blue")
echo "Starting blue-green deployment..."
echo "Current color: $CURRENT_COLOR"
echo "Deploying to: $INACTIVE_COLOR"
echo "New AMI ID: $NEW_AMI_ID"
cd "environments/$ENVIRONMENT"
# Step 1: Update the inactive deployment
echo "Step 1: Deploying new version to $INACTIVE_COLOR environment..."
terraform apply \
-target="module.${INACTIVE_COLOR}_deployment" \
-var="deployment_color=$INACTIVE_COLOR" \
-var="${INACTIVE_COLOR}_ami_id=$NEW_AMI_ID" \
-auto-approve
# Step 2: Health check the new deployment
echo "Step 2: Performing health checks..."
sleep 30
# Get the inactive deployment target group ARN
INACTIVE_TG_ARN=$(terraform output -raw "${INACTIVE_COLOR}_target_group_arn")
# Check health of targets (simplified example)
aws elbv2 describe-target-health --target-group-arn "$INACTIVE_TG_ARN" \
| jq -r '.TargetHealthDescriptions[].TargetHealth.State' \
| grep -q "healthy" || {
echo "Health check failed! Rolling back..."
terraform apply \
-target="module.${INACTIVE_COLOR}_deployment" \
-var="deployment_color=$CURRENT_COLOR" \
-var="${INACTIVE_COLOR}_ami_id=" \
-auto-approve
exit 1
}
echo "Health checks passed!"
# Step 3: Gradually shift traffic
echo "Step 3: Shifting traffic..."
for weight in 10 25 50 75 100; do
echo "Setting traffic weight to $weight% on $INACTIVE_COLOR..."
terraform apply \
-target="aws_lb_listener_rule.blue" \
-var="deployment_color=$INACTIVE_COLOR" \
-var="traffic_weight=$weight" \
-auto-approve
echo "Waiting 2 minutes before next traffic shift..."
sleep 120
# Additional health checks could go here
done
# Step 4: Shutdown old deployment
echo "Step 4: Shutting down old $CURRENT_COLOR deployment..."
terraform apply \
-target="module.${CURRENT_COLOR}_deployment" \
-var="deployment_color=$INACTIVE_COLOR" \
-auto-approve
echo "Blue-green deployment completed successfully!"
echo "Active deployment is now: $INACTIVE_COLOR"
Targeted Rollback Strategies
Safe Rollback Patterns
# rollback.tf
variable "enable_rollback_mode" {
description = "Enable rollback mode to restore previous state"
type = bool
default = false
}
variable "rollback_ami_id" {
description = "AMI ID to rollback to"
type = string
default = ""
}
variable "rollback_target" {
description = "Target for rollback (compute, database, all)"
type = string
default = "all"
validation {
condition = contains(["compute", "database", "network", "all"], var.rollback_target)
error_message = "Rollback target must be one of: compute, database, network, all."
}
}
locals {
# Use rollback AMI if in rollback mode, otherwise use current AMI
active_ami_id = var.enable_rollback_mode && var.rollback_ami_id != "" ? var.rollback_ami_id : var.ami_id
# Conditional configurations for rollback
rollback_config = var.enable_rollback_mode ? {
skip_health_checks = true
force_update = true
ignore_tags = true
} : {
skip_health_checks = false
force_update = false
ignore_tags = false
}
}
# Compute resources with rollback support
resource "aws_launch_template" "app" {
name_prefix = "${var.project_name}-app-"
image_id = local.active_ami_id
instance_type = var.instance_type
vpc_security_group_ids = [aws_security_group.app.id]
# Add rollback metadata
user_data = base64encode(templatefile("${path.module}/user_data.sh", {
app_version = var.enable_rollback_mode ? "rollback" : var.app_version
rollback_mode = var.enable_rollback_mode
deployment_id = var.enable_rollback_mode ? "rollback-${timestamp()}" : var.deployment_id
}))
lifecycle {
create_before_destroy = true
ignore_changes = var.enable_rollback_mode ? [] : [user_data]
}
tag_specifications {
resource_type = "instance"
tags = merge(var.common_tags, {
Name = "${var.project_name}-app"
RollbackMode = var.enable_rollback_mode
})
}
}
Rollback Scripts
#!/bin/bash
# rollback.sh
set -e
ROLLBACK_TARGET=${1:-all}
ROLLBACK_AMI_ID=${2}
ENVIRONMENT=${3:-dev}
echo "Starting rollback procedure..."
echo "Target: $ROLLBACK_TARGET"
echo "Environment: $ENVIRONMENT"
cd "environments/$ENVIRONMENT"
# Get current state backup
echo "Creating state backup..."
cp terraform.tfstate "terraform.tfstate.backup.$(date +%Y%m%d_%H%M%S)"
case $ROLLBACK_TARGET in
"compute")
echo "Rolling back compute resources..."
if [ -n "$ROLLBACK_AMI_ID" ]; then
terraform apply \
-target=module.infrastructure.module.compute \
-var="enable_rollback_mode=true" \
-var="rollback_ami_id=$ROLLBACK_AMI_ID" \
-auto-approve
else
echo "Error: AMI ID required for compute rollback"
exit 1
fi
;;
"database")
echo "Rolling back database resources..."
# Database rollbacks typically require point-in-time recovery
echo "Warning: Database rollback requires manual intervention"
echo "Please use AWS RDS point-in-time recovery or restore from backup"
;;
"network")
echo "Rolling back network resources..."
# Network rollbacks are usually safe but may cause downtime
terraform apply \
-target=module.infrastructure.module.vpc \
-target=module.infrastructure.module.security \
-var="enable_rollback_mode=true" \
-auto-approve
;;
"all")
echo "Rolling back all resources..."
terraform apply \
-var="enable_rollback_mode=true" \
-var="rollback_ami_id=$ROLLBACK_AMI_ID" \
-auto-approve
;;
*)
echo "Unknown rollback target: $ROLLBACK_TARGET"
exit 1
;;
esac
echo "Rollback completed!"
echo "Please verify the rollback was successful before proceeding."
Troubleshooting with Targeting
Debugging Specific Resources
# Debug specific resource
terraform plan -target=aws_instance.web[0] -out=debug.tfplan
terraform show debug.tfplan
# Check dependencies for a specific resource
terraform graph -target=aws_instance.web[0] | dot -Tpng > dependencies.png
# Force refresh specific resource
terraform apply -target=aws_instance.web[0] -refresh-only
# Import existing resource into state
terraform import aws_instance.web[0] i-1234567890abcdef0
# Remove specific resource from state without destroying
terraform state rm aws_instance.web[0]
# Taint specific resource to force recreation
terraform taint aws_instance.web[0]
terraform apply -target=aws_instance.web[0]
State Surgery with Targeting
#!/bin/bash
# state-surgery.sh - Advanced state manipulation
OPERATION=${1}
RESOURCE=${2}
NEW_ADDRESS=${3}
case $OPERATION in
"move")
echo "Moving resource from $RESOURCE to $NEW_ADDRESS"
terraform state mv "$RESOURCE" "$NEW_ADDRESS"
;;
"remove")
echo "Removing resource $RESOURCE from state"
terraform state rm "$RESOURCE"
;;
"import")
echo "Importing resource $NEW_ADDRESS with ID $RESOURCE"
terraform import "$NEW_ADDRESS" "$RESOURCE"
;;
"replace")
echo "Replacing resource $RESOURCE"
terraform apply -replace="$RESOURCE" -target="$RESOURCE"
;;
*)
echo "Usage: $0 <move|remove|import|replace> <resource> [new_address]"
exit 1
;;
esac
Best Practices
1. Safe Targeting Practices
# Always plan before applying with targets
terraform plan -target=aws_instance.web -out=targeted.tfplan
terraform apply targeted.tfplan
# Use specific targets rather than wildcards when possible
terraform plan -target=aws_instance.web[0] # Good
terraform plan -target=aws_instance.web # Less precise
# Document your targeting strategy
echo "Applying database changes only" > deployment.log
terraform apply -target=module.database >> deployment.log 2>&1
2. Dependency Management
# Explicit dependencies for targeting
resource "aws_instance" "app" {
count = var.instance_count
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
subnet_id = aws_subnet.private[count.index % length(aws_subnet.private)].id
vpc_security_group_ids = [aws_security_group.app.id]
# Explicit dependency ensures proper targeting order
depends_on = [
aws_nat_gateway.main,
aws_route_table_association.private
]
tags = {
Name = "${var.project_name}-app-${count.index + 1}"
}
}
3. Testing Targeting Strategies
#!/bin/bash
# test-targeting.sh
echo "Testing targeting strategies..."
# Test 1: Plan with targeting to ensure no unexpected changes
echo "Test 1: Verify targeted plan"
terraform plan -target=module.compute -detailed-exitcode
if [ $? -eq 2 ]; then
echo "✓ Changes detected as expected"
else
echo "✗ No changes detected - check configuration"
fi
# Test 2: Validate dependencies
echo "Test 2: Validate dependencies"
terraform validate
# Test 3: Dry run with targeting
echo "Test 3: Dry run targeted apply"
terraform plan -target=module.compute -out=test.tfplan
terraform show -json test.tfplan | jq '.planned_values'
echo "All tests completed!"
Key Takeaways
- Precision: Use specific resource targeting for precise control over deployments
- Safety: Always plan before applying targeted changes
- Dependencies: Understand resource dependencies when targeting
- Documentation: Document targeting strategies and rationale
- Testing: Test targeting strategies in non-production environments first
- Rollback: Have rollback plans when using targeting for deployments
Next Steps
- Tutorial 25: Learn about configuration validation and best practices
- Practice targeting strategies in different scenarios
- Experiment with blue-green deployments using targeting
- Review the Terraform CLI Documentation