Navigation
AnsibleUpdated July 3, 2026

Terraform Automation

terraformiacautomationazure-providerstate-managementmodulescicdepicazureinfrastructure

Terraform Automation

Overview

Terraform serves as our primary Infrastructure as Code (IaC) tool for provisioning and managing Azure resources in the Epic on Azure environment. This document covers Terraform automation strategies, best practices, and integration patterns specific to our Epic workloads.

Terraform in Epic on Azure

Why Terraform?

  • Multi-Cloud Support: Future-proofs our infrastructure strategy
  • State Management: Reliable tracking of resource lifecycles
  • Plan/Apply Workflow: Safe, predictable infrastructure changes
  • Rich Azure Provider: Comprehensive Azure resource coverage
  • Module Ecosystem: Reusable infrastructure components

Epic-Specific Benefits

  • Large Scale Management: Handles thousands of VMs and complex networking
  • Environment Consistency: Identical infrastructure across dev/test/prod
  • Compliance: Declarative policies and audit trails
  • Integration: Seamless workflow with Ansible and CI/CD

Architecture

graph TB
    A[Terraform Enterprise] --> B[Workspaces]
    B --> C[Epic Production]
    B --> D[Epic Non-Production]
    B --> E[Epic Shared Services]

    C --> F[Private Module Registry]
    D --> F
    E --> F

    F --> G[VM Modules]
    F --> H[Network Modules]
    F --> I[Storage Modules]
    F --> J[Security Modules]

    C --> K[Azure Resources]
    D --> K
    E --> K

    K --> L[Post-Provisioning]
    L --> M[Ansible Configuration]

Terraform Enterprise Workspaces

Our Terraform infrastructure is organized into logical workspaces:

Production Workspaces

  • epic-prod-east: East region production environment
  • epic-prod-west: West region production environment
  • epic-prod-shared: Shared services (networking, DNS, monitoring)

Non-Production Workspaces

  • epic-dev-east: Development environment
  • epic-test-east: Testing environment
  • epic-staging-east: Staging environment
  • epic-training-east: Training environment

Infrastructure Workspaces

  • epic-networking: Core networking components
  • epic-security: Security services and policies
  • epic-monitoring: Monitoring and logging infrastructure

Key Patterns

1. Modular Infrastructure

module "epic_vm" {
  source = "app.terraform.io/optum-epic/vm/azurerm"
  version = "~> 2.0"

  # Epic-specific configurations
  epic_application = "hyperspace"
  epic_environment = var.environment
  epic_region     = var.azure_region

  # VM specifications
  vm_count        = var.hyperspace_vm_count
  vm_size         = var.hyperspace_vm_size
  vm_image        = var.epic_golden_image

  # Networking
  subnet_id       = module.networking.epic_app_subnet_id

  # Tags
  tags = local.epic_tags
}

2. Environment-Specific Variables

# variables.tf
variable "environment" {
  description = "Environment name (prod, test, dev)"
  type        = string
  validation {
    condition = contains(["prod", "test", "dev", "staging"], var.environment)
    error_message = "Environment must be prod, test, dev, or staging."
  }
}

variable "epic_vm_sizes" {
  description = "VM sizes by environment"
  type = map(object({
    hyperspace = string
    chronicles = string
    clarity    = string
  }))
  default = {
    prod = {
      hyperspace = "Standard_D8s_v3"
      chronicles = "Standard_D16s_v3"
      clarity    = "Standard_D4s_v3"
    }
    test = {
      hyperspace = "Standard_D4s_v3"
      chronicles = "Standard_D8s_v3"
      clarity    = "Standard_D2s_v3"
    }
  }
}

3. Epic-Specific Resource Tagging

locals {
  epic_tags = {
    # Epic-specific tags
    "epic-application" = var.epic_application
    "epic-environment" = var.environment
    "epic-stamp"      = var.epic_stamp
    "epic-tier"       = var.epic_tier

    # Compliance tags
    "data-classification" = "PHI"
    "backup-required"     = "true"
    "monitoring-level"    = var.epic_monitoring_level

    # Operational tags
    "maintenance-window"  = var.maintenance_window
    "patch-schedule"     = var.patch_schedule
    "dr-tier"           = var.dr_tier

    # Azure governance tags
    "cost-center"       = var.cost_center
    "technical-owner"   = var.technical_owner
    "business-owner"    = var.business_owner
  }
}

4. Networking Configuration

# Epic-specific networking patterns
resource "azurerm_virtual_network" "epic" {
  name                = "vnet-epic-${var.environment}-${var.region}"
  address_space       = [var.epic_vnet_cidr]
  location            = var.azure_region
  resource_group_name = azurerm_resource_group.epic.name

  tags = local.epic_tags
}

resource "azurerm_subnet" "epic_app" {
  name                 = "subnet-epic-app-${var.environment}"
  resource_group_name  = azurerm_resource_group.epic.name
  virtual_network_name = azurerm_virtual_network.epic.name
  address_prefixes     = [var.epic_app_subnet_cidr]

  # Epic security requirements
  enforce_private_link_endpoint_network_policies = true
  enforce_private_link_service_network_policies  = true
}

resource "azurerm_subnet" "epic_db" {
  name                 = "subnet-epic-db-${var.environment}"
  resource_group_name  = azurerm_resource_group.epic.name
  virtual_network_name = azurerm_virtual_network.epic.name
  address_prefixes     = [var.epic_db_subnet_cidr]

  # Database subnet delegation
  delegation {
    name = "epic-db-delegation"
    service_delegation {
      name    = "Microsoft.DBforMySQL/flexibleServers"
      actions = ["Microsoft.Network/virtualNetworks/subnets/join/action"]
    }
  }
}

Private Module Registry

Our Epic-specific modules are stored in the Terraform Enterprise private registry:

Available Modules

  • vm-windows: Epic Windows VM with standard configuration
  • vm-linux: Epic Linux VM with standard configuration
  • networking: Epic networking patterns (VNets, subnets, NSGs)
  • storage: Epic storage accounts and file shares
  • database: Epic database configurations (SQL Server, MySQL)
  • monitoring: Epic monitoring and alerting setup
  • security: Epic security configurations (Key Vault, policies)

Module Usage Example

module "epic_hyperspace_vms" {
  source  = "app.terraform.io/optum-epic/vm-windows/azurerm"
  version = "~> 3.1"

  # Epic application configuration
  epic_application    = "hyperspace"
  epic_component     = "app-server"
  epic_environment   = var.environment

  # VM configuration
  vm_count           = var.hyperspace_vm_count
  vm_size           = local.epic_vm_sizes[var.environment].hyperspace
  vm_admin_username = var.epic_admin_username

  # Networking
  subnet_id          = data.azurerm_subnet.epic_app.id

  # Storage
  os_disk_size_gb    = 128
  data_disk_size_gb  = 512

  # Epic-specific settings
  install_epic_agents = true
  join_domain        = true
  domain_name        = var.epic_domain_name

  tags = local.epic_tags
}

Automation Workflows

1. CI/CD Integration

# .github/workflows/terraform.yml
name: Terraform Epic Infrastructure

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  terraform:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: 1.5.0
          cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}

      - name: Terraform Format Check
        run: terraform fmt -check -recursive

      - name: Terraform Init
        run: terraform init

      - name: Terraform Validate
        run: terraform validate

      - name: Terraform Plan
        run: terraform plan -out=tfplan
        env:
          TF_VAR_epic_environment: ${{ github.ref_name }}

      - name: Terraform Apply
        if: github.ref == 'refs/heads/main'
        run: terraform apply tfplan

2. Epic-Specific Validation

# validation.tf
# Epic VM naming validation
resource "terraform_data" "epic_vm_naming_validation" {
  for_each = var.epic_vms

  lifecycle {
    precondition {
      condition = can(regex("^epic-[a-z]+-[a-z]+-[0-9]+$", each.value.name))
      error_message = "Epic VM names must follow pattern: epic-{app}-{env}-{number}"
    }

    precondition {
      condition = contains(["prod", "test", "dev", "staging"], each.value.environment)
      error_message = "Epic VMs must have valid environment designation."
    }

    precondition {
      condition = each.value.data_classification == "PHI"
      error_message = "Epic VMs must be classified as PHI data."
    }
  }
}

# Epic compliance validation
check "epic_compliance" {
  assert {
    condition = alltrue([
      for vm in var.epic_vms :
      vm.backup_enabled == true
    ])
    error_message = "All Epic VMs must have backup enabled."
  }

  assert {
    condition = alltrue([
      for vm in var.epic_vms :
      vm.monitoring_enabled == true
    ])
    error_message = "All Epic VMs must have monitoring enabled."
  }
}

3. Post-Deployment Automation

# outputs.tf
output "epic_vm_details" {
  description = "Epic VM information for Ansible inventory"
  value = {
    for vm in azurerm_windows_virtual_machine.epic :
    vm.name => {
      private_ip     = vm.private_ip_address
      public_ip      = vm.public_ip_address
      epic_app       = vm.tags["epic-application"]
      epic_env       = vm.tags["epic-environment"]
      epic_tier      = vm.tags["epic-tier"]
      ansible_groups = [
        "epic_${vm.tags["epic-application"]}",
        "epic_${vm.tags["epic-environment"]}",
        "epic_windows"
      ]
    }
  }
  sensitive = false
}

# Integration with Ansible
resource "local_file" "ansible_inventory" {
  content = templatefile("${path.module}/templates/inventory.tpl", {
    epic_vms = azurerm_windows_virtual_machine.epic
  })
  filename = "${path.module}/generated/epic-inventory.yml"

  provisioner "local-exec" {
    command = "ansible-playbook -i ${self.filename} epic-post-deploy.yml"
  }
}

Best Practices

Security

  • State Encryption: Terraform Enterprise handles state encryption
  • Secret Management: Use Azure Key Vault data sources, never hardcode secrets
  • RBAC: Implement workspace-level access controls
  • Audit Logging: Enable comprehensive audit trails

Reliability

  • State Locking: Terraform Enterprise provides automatic state locking
  • Backup Strategy: Regular state backups and disaster recovery procedures
  • Rollback Plans: Prepare rollback procedures for failed deployments
  • Testing: Validate modules in non-production environments first

Performance

  • Parallel Execution: Use -parallelism flag for large deployments
  • State Optimization: Keep state files reasonably sized
  • Resource Targeting: Use -target for selective updates
  • Module Versioning: Pin module versions for consistency

Integration Points

With Ansible

# Trigger Ansible after VM creation
resource "null_resource" "epic_vm_configuration" {
  depends_on = [azurerm_windows_virtual_machine.epic]

  triggers = {
    vm_ids = join(",", azurerm_windows_virtual_machine.epic[*].id)
  }

  provisioner "local-exec" {
    command = <<EOF
      ansible-playbook \
        -i ${local_file.ansible_inventory.filename} \
        --extra-vars "target_vms=epic_${var.epic_application}" \
        epic-vm-setup.yml
    EOF
  }
}

With Azure DevOps

# Azure DevOps pipeline trigger
resource "azuredevops_build_definition" "epic_deployment" {
  project_id = var.azdo_project_id
  name       = "Epic-${var.epic_application}-Deploy"

  repository {
    repo_type   = "TfsGit"
    repo_id     = var.azdo_repo_id
    branch_name = "main"
  }

  ci_trigger {
    use_yaml = true
  }

  variable {
    name  = "TF_WORKSPACE"
    value = terraform.workspace
  }
}

With Monitoring

# Automatic monitoring setup
resource "azurerm_monitor_diagnostic_setting" "epic_vm" {
  for_each = azurerm_windows_virtual_machine.epic

  name                       = "epic-vm-diagnostics"
  target_resource_id         = each.value.id
  log_analytics_workspace_id = data.azurerm_log_analytics_workspace.epic.id

  enabled_log {
    category = "Administrative"
  }

  enabled_log {
    category = "Security"
  }

  metric {
    category = "AllMetrics"
  }
}

Troubleshooting

Common Issues

  • State Lock Conflicts: Use terraform force-unlock carefully
  • Provider Version Conflicts: Pin provider versions
  • Resource Dependencies: Use explicit depends_on when needed
  • Azure Quota Limits: Monitor subscription quotas

Debugging Tips

  • Verbose Logging: Set TF_LOG=DEBUG for detailed output
  • Plan Analysis: Always review plans before applying
  • State Inspection: Use terraform show and terraform state list
  • Import Existing Resources: Use terraform import for brownfield scenarios

Related Documentation

Support

  • Terraform Enterprise: Contact platform team for workspace access
  • Module Development: Contribute to private registry
  • Training: Regular Terraform workshops and certification programs
  • Community: Internal Terraform user groups and best practice sharing