AskLearn
Loading...
← Back to Terraform Course
IntermediateFundamentals

Output Values

Export configuration data

Tutorial 8: Output Values

Learning Objectives

  • Understand the purpose and benefits of output values
  • Learn to define and use outputs in Terraform configurations
  • Practice exposing important information from your infrastructure
  • Integrate outputs with modules and remote state

What are Output Values?

Output values are a way to expose information about your infrastructure after Terraform has created or modified resources. They serve as the return values of a Terraform configuration and can be used by other configurations or displayed to users.

Purpose of Outputs

  • Information Sharing: Share resource details with other configurations
  • User Interface: Display important information to users
  • Module Integration: Return values from modules
  • Automation: Provide data for scripts and other tools
  • Documentation: Self-document important infrastructure details

Outputs vs Variables

# Variables are inputs to your configuration
variable "instance_type" {
  type = string
}

# Outputs are results from your configuration
output "instance_id" {
  value = aws_instance.web.id
}

Output Value Syntax

Basic Output Block

output "output_name" {
  description = "Description of the output value"
  value       = expression
  sensitive   = false  # Optional, defaults to false
}

Output Components

  • Name: Identifier for the output
  • Description: Human-readable explanation
  • Value: The expression to output
  • Sensitive: Whether to hide the value in logs

Simple Output Examples

Basic Resource Attributes

# outputs.tf
output "instance_id" {
  description = "ID of the EC2 instance"
  value       = aws_instance.web.id
}

output "instance_public_ip" {
  description = "Public IP address of the EC2 instance"
  value       = aws_instance.web.public_ip
}

output "instance_private_ip" {
  description = "Private IP address of the EC2 instance"
  value       = aws_instance.web.private_ip
}

output "instance_arn" {
  description = "ARN of the EC2 instance"
  value       = aws_instance.web.arn
}

URL Construction

output "website_url" {
  description = "URL to access the website"
  value       = "http://${aws_instance.web.public_ip}"
}

output "ssh_connection" {
  description = "SSH connection string"
  value       = "ssh ec2-user@${aws_instance.web.public_ip}"
}

output "website_endpoint" {
  description = "Website endpoint with custom port"
  value       = "http://${aws_instance.web.public_ip}:8080"
}

Complex Output Examples

Lists and Maps

# Output list of instance IDs
output "instance_ids" {
  description = "List of instance IDs"
  value       = aws_instance.web[*].id
}

# Output map of instance details
output "instance_details" {
  description = "Map of instance details"
  value = {
    for instance in aws_instance.web :
    instance.tags.Name => {
      id        = instance.id
      public_ip = instance.public_ip
      az        = instance.availability_zone
    }
  }
}

# Output availability zones
output "availability_zones" {
  description = "List of availability zones used"
  value       = data.aws_availability_zones.available.names
}

Database Connection Information

# Database outputs
output "database_endpoint" {
  description = "Database endpoint"
  value       = aws_db_instance.main.endpoint
}

output "database_connection_string" {
  description = "Database connection string"
  value = "mysql://${aws_db_instance.main.username}:${var.db_password}@${aws_db_instance.main.endpoint}:${aws_db_instance.main.port}/${aws_db_instance.main.db_name}"
  sensitive = true
}

output "database_info" {
  description = "Database connection information"
  value = {
    endpoint = aws_db_instance.main.endpoint
    port     = aws_db_instance.main.port
    database = aws_db_instance.main.db_name
    username = aws_db_instance.main.username
  }
}

Load Balancer Outputs

output "load_balancer_dns" {
  description = "DNS name of the load balancer"
  value       = aws_lb.web.dns_name
}

output "load_balancer_zone_id" {
  description = "Hosted zone ID of the load balancer"
  value       = aws_lb.web.zone_id
}

output "load_balancer_url" {
  description = "URL of the load balancer"
  value       = "https://${aws_lb.web.dns_name}"
}

output "target_group_arn" {
  description = "ARN of the target group"
  value       = aws_lb_target_group.web.arn
}

Conditional Outputs

Using count

variable "create_load_balancer" {
  type    = bool
  default = false
}

resource "aws_lb" "web" {
  count = var.create_load_balancer ? 1 : 0
  # ... configuration
}

output "load_balancer_dns" {
  description = "DNS name of the load balancer (if created)"
  value       = var.create_load_balancer ? aws_lb.web[0].dns_name : null
}

# Alternative using try()
output "load_balancer_dns_safe" {
  description = "DNS name of the load balancer (if created)"
  value       = try(aws_lb.web[0].dns_name, null)
}

Using for_each

variable "environments" {
  type    = list(string)
  default = ["dev", "staging", "prod"]
}

resource "aws_instance" "web" {
  for_each = toset(var.environments)
  
  ami           = data.aws_ami.amazon_linux.id
  instance_type = "t2.micro"
  
  tags = {
    Name        = "web-${each.value}"
    Environment = each.value
  }
}

output "environment_instances" {
  description = "Instance details by environment"
  value = {
    for env, instance in aws_instance.web :
    env => {
      id        = instance.id
      public_ip = instance.public_ip
      url       = "http://${instance.public_ip}"
    }
  }
}

Sensitive Outputs

Marking Outputs as Sensitive

output "database_password" {
  description = "Database password"
  value       = var.db_password
  sensitive   = true
}

output "api_key" {
  description = "API key for external service"
  value       = random_password.api_key.result
  sensitive   = true
}

output "ssl_certificate" {
  description = "SSL certificate content"
  value       = tls_private_key.example.private_key_pem
  sensitive   = true
}

Viewing Sensitive Outputs

# Sensitive values are hidden in normal output
terraform output

# View specific sensitive output
terraform output database_password

# Show all outputs including sensitive (in JSON)
terraform output -json

Using Outputs

Viewing Outputs

# Show all outputs
terraform output

# Show specific output
terraform output instance_id

# Show output in JSON format
terraform output -json

# Show specific output in raw format (no quotes)
terraform output -raw website_url

Output Examples

$ terraform output

instance_id = "i-0123456789abcdef0"
instance_public_ip = "54.123.45.67"
website_url = "http://54.123.45.67"

$ terraform output instance_id
"i-0123456789abcdef0"

$ terraform output -raw website_url
http://54.123.45.67

Using Outputs in Scripts

#!/bin/bash
# deploy.sh

# Get outputs from Terraform
INSTANCE_IP=$(terraform output -raw instance_public_ip)
WEBSITE_URL=$(terraform output -raw website_url)

# Use in deployment script
echo "Deploying to instance: $INSTANCE_IP"
curl -f $WEBSITE_URL || echo "Website not responding"

# Update DNS or load balancer
aws route53 change-resource-record-sets \
  --hosted-zone-id Z123456789 \
  --change-batch "{
    \"Changes\": [{
      \"Action\": \"UPSERT\",
      \"ResourceRecordSet\": {
        \"Name\": \"app.example.com\",
        \"Type\": \"A\",
        \"TTL\": 300,
        \"ResourceRecords\": [{\"Value\": \"$INSTANCE_IP\"}]
      }
    }]
  }"

Module Outputs

Module with Outputs

# modules/web-server/outputs.tf
output "instance_id" {
  description = "ID of the web server instance"
  value       = aws_instance.web.id
}

output "public_ip" {
  description = "Public IP of the web server"
  value       = aws_instance.web.public_ip
}

output "security_group_id" {
  description = "ID of the security group"
  value       = aws_security_group.web.id
}

output "web_server_info" {
  description = "Complete web server information"
  value = {
    id                = aws_instance.web.id
    public_ip         = aws_instance.web.public_ip
    private_ip        = aws_instance.web.private_ip
    security_group_id = aws_security_group.web.id
    availability_zone = aws_instance.web.availability_zone
  }
}

Using Module Outputs

# main.tf
module "web_server" {
  source = "./modules/web-server"
  
  instance_type = "t2.micro"
  environment   = "production"
}

# Reference module outputs
output "web_instance_id" {
  description = "Web server instance ID"
  value       = module.web_server.instance_id
}

output "web_public_ip" {
  description = "Web server public IP"
  value       = module.web_server.public_ip
}

# Use module output in other resources
resource "aws_route53_record" "web" {
  zone_id = data.aws_route53_zone.main.zone_id
  name    = "web.example.com"
  type    = "A"
  ttl     = 300
  records = [module.web_server.public_ip]
}

Advanced Output Patterns

Computed Values

locals {
  instance_count = length(aws_instance.web)
  total_storage  = sum([for i in aws_instance.web : i.root_block_device[0].volume_size])
  
  environment_info = {
    name           = var.environment
    region         = var.region
    instance_count = local.instance_count
    total_storage  = local.total_storage
    created_at     = timestamp()
  }
}

output "deployment_summary" {
  description = "Summary of the deployment"
  value       = local.environment_info
}

output "cost_estimate" {
  description = "Estimated monthly cost"
  value = {
    compute_hours = local.instance_count * 24 * 30
    storage_gb    = local.total_storage
    estimated_usd = local.instance_count * 8.76 + local.total_storage * 0.10
  }
}

Dynamic Outputs with Functions

# Generate connection strings for all instances
output "ssh_connections" {
  description = "SSH connection strings for all instances"
  value = [
    for instance in aws_instance.web :
    "ssh -i ~/.ssh/key.pem ec2-user@${instance.public_ip}"
  ]
}

# Generate environment-specific URLs
output "environment_urls" {
  description = "URLs for different environments"
  value = {
    for env, instance in aws_instance.web :
    env => "https://${env}.example.com"
  }
}

# Format database connection parameters
output "database_connections" {
  description = "Database connection strings by type"
  value = {
    jdbc_url    = "jdbc:mysql://${aws_db_instance.main.endpoint}:${aws_db_instance.main.port}/${aws_db_instance.main.db_name}"
    python_url  = "mysql+pymysql://${aws_db_instance.main.username}:${urlencode(var.db_password)}@${aws_db_instance.main.endpoint}:${aws_db_instance.main.port}/${aws_db_instance.main.db_name}"
    node_config = {
      host     = aws_db_instance.main.address
      port     = aws_db_instance.main.port
      database = aws_db_instance.main.db_name
      username = aws_db_instance.main.username
    }
  }
  sensitive = true
}

Infrastructure Documentation

output "infrastructure_map" {
  description = "Complete infrastructure mapping"
  value = {
    vpc = {
      id         = aws_vpc.main.id
      cidr_block = aws_vpc.main.cidr_block
      region     = var.region
    }
    
    subnets = {
      for subnet in aws_subnet.public :
      subnet.tags.Name => {
        id                = subnet.id
        cidr_block        = subnet.cidr_block
        availability_zone = subnet.availability_zone
      }
    }
    
    instances = {
      for instance in aws_instance.web :
      instance.tags.Name => {
        id               = instance.id
        type             = instance.instance_type
        ami              = instance.ami
        public_ip        = instance.public_ip
        private_ip       = instance.private_ip
        subnet_id        = instance.subnet_id
        security_groups  = instance.vpc_security_group_ids
      }
    }
    
    load_balancers = var.create_load_balancer ? {
      web = {
        dns_name = aws_lb.web[0].dns_name
        arn      = aws_lb.web[0].arn
        type     = aws_lb.web[0].load_balancer_type
      }
    } : {}
  }
}

Remote State and Outputs

Consuming Remote State Outputs

# In another configuration, reference outputs from remote state
data "terraform_remote_state" "vpc" {
  backend = "s3"
  
  config = {
    bucket = "my-terraform-state"
    key    = "vpc/terraform.tfstate"
    region = "us-west-2"
  }
}

# Use remote state outputs
resource "aws_instance" "app" {
  ami           = data.aws_ami.amazon_linux.id
  instance_type = "t2.micro"
  subnet_id     = data.terraform_remote_state.vpc.outputs.private_subnet_id
  
  vpc_security_group_ids = [
    data.terraform_remote_state.vpc.outputs.app_security_group_id
  ]
}

Organizing Outputs for Remote State

# vpc/outputs.tf - Expose values for other configurations
output "vpc_id" {
  description = "ID of the VPC"
  value       = aws_vpc.main.id
}

output "private_subnet_ids" {
  description = "List of private subnet IDs"
  value       = aws_subnet.private[*].id
}

output "public_subnet_ids" {
  description = "List of public subnet IDs"
  value       = aws_subnet.public[*].id
}

output "app_security_group_id" {
  description = "Security group ID for applications"
  value       = aws_security_group.app.id
}

output "database_subnet_group_name" {
  description = "Name of the database subnet group"
  value       = aws_db_subnet_group.main.name
}

Best Practices

1. Organize Outputs by Purpose

# Group related outputs
output "network_info" {
  description = "Network configuration details"
  value = {
    vpc_id     = aws_vpc.main.id
    subnet_ids = aws_subnet.public[*].id
    igw_id     = aws_internet_gateway.main.id
  }
}

output "compute_info" {
  description = "Compute resource details"
  value = {
    instance_ids = aws_instance.web[*].id
    public_ips   = aws_instance.web[*].public_ip
    private_ips  = aws_instance.web[*].private_ip
  }
}

2. Use Descriptive Names and Descriptions

# Good
output "web_server_public_ip" {
  description = "Public IP address of the web server for SSH access"
  value       = aws_instance.web.public_ip
}

# Avoid
output "ip" {
  value = aws_instance.web.public_ip
}

3. Include Connection Information

output "connection_info" {
  description = "Information needed to connect to services"
  value = {
    ssh_command     = "ssh -i ~/.ssh/key.pem ec2-user@${aws_instance.web.public_ip}"
    website_url     = "http://${aws_instance.web.public_ip}"
    database_host   = aws_db_instance.main.endpoint
    database_port   = aws_db_instance.main.port
  }
}

4. Handle Missing Values Gracefully

output "optional_load_balancer" {
  description = "Load balancer DNS (if created)"
  value       = var.create_load_balancer ? aws_lb.web[0].dns_name : "Not created"
}

output "backup_info" {
  description = "Backup configuration"
  value = {
    enabled    = var.enable_backups
    bucket     = var.enable_backups ? aws_s3_bucket.backups[0].id : null
    schedule   = var.enable_backups ? var.backup_schedule : null
  }
}

5. Document Output Usage

output "api_endpoint" {
  description = <<-EOF
    API endpoint URL for the application.
    Use this URL to configure client applications.
    Example: curl ${aws_lb.api.dns_name}/health
  EOF
  value = "https://${aws_lb.api.dns_name}"
}

Testing and Validation

Validate Output Values

# Test outputs after apply
terraform apply

# Verify specific outputs
terraform output website_url
curl $(terraform output -raw website_url)

# Test database connection
terraform output -json database_info | jq '.host'

Output Testing Script

#!/bin/bash
# test-outputs.sh

echo "Testing Terraform outputs..."

# Get all outputs
terraform output -json > outputs.json

# Test web server
WEBSITE_URL=$(jq -r '.website_url.value' outputs.json)
echo "Testing website: $WEBSITE_URL"
curl -f "$WEBSITE_URL" || echo "Website test failed"

# Test SSH connection
INSTANCE_IP=$(jq -r '.instance_public_ip.value' outputs.json)
echo "Testing SSH to: $INSTANCE_IP"
ssh -o ConnectTimeout=5 -o BatchMode=yes ec2-user@$INSTANCE_IP exit || echo "SSH test failed"

# Test database connection
DB_HOST=$(jq -r '.database_endpoint.value' outputs.json)
echo "Testing database connection to: $DB_HOST"
nc -z "$DB_HOST" 3306 || echo "Database connection test failed"

echo "Output testing complete"

Key Takeaways

  • Outputs expose important information about your infrastructure
  • Use descriptive names and comprehensive descriptions
  • Organize outputs logically by purpose or component
  • Mark sensitive outputs appropriately
  • Outputs enable integration between configurations and modules
  • Use outputs to create self-documenting infrastructure
  • Test outputs to ensure they provide correct information
  • Structure outputs for easy consumption by scripts and other tools

Next Steps

  1. Complete Tutorial 9: Store Remote State
  2. Learn about local values and expressions
  3. Explore modules and their input/output patterns
  4. Practice with complex output scenarios

Additional Resources