Skip to content

Terraform Modules — HISD Full-Stack

Overview

The infrastructure-as-code lives in eshars-ops/develop/infrastructure/ and uses OpenTofu (Terraform-compatible). A single tofu apply provisions everything needed to run the HISD web portal in a new environment.

infrastructure/
├── main.tf # Wires all modules together
├── variables.tf # Input variables
├── locals.tf # Environment-specific logic
├── outputs.tf # Resource outputs
├── environments/
│ ├── dev.tfvars
│ ├── qa.tfvars
│ ├── uat.tfvars
│ ├── staging.tfvars
│ ├── prod.tfvars
│ ├── training.tfvars
│ └── regression.tfvars
└── modules/
├── app-service-plan/ # (pre-existing)
├── key-vault/ # (pre-existing)
├── sql-server/ # (pre-existing)
├── private-dns/ # (pre-existing)
├── monitoring/ # (pre-existing)
├── app-insights/ # Log Analytics + Application Insights
├── sql-database/ # 3 databases + optional elastic pool
├── storage/ # Storage Account + 6 blob containers
├── redis/ # Redis Cache
├── web-app/ # Windows App Service + staging slot
├── function-app/ # Windows Function App
└── key-vault-secrets/ # Seeds KV with connection strings

SQL Server Topology

Not every environment gets its own SQL Server. The topology balances cost and isolation:

SQL ServerEnvironmentsMode
eshars-nonprod-sqlDev, QA, TrainingElastic pool (50 eDTU shared)
eshars-uat-sqlUATDedicated (standalone SKUs)
eshars-regression-sqlRegressionDedicated (standalone SKUs)
eshars-prod-sqlProd, StagingDedicated (standalone SKUs)

Each environment gets 3 databases on its assigned server:

DatabasePurpose
db-eshars-{env}Core — students, visits, clinicians, Quartz scheduler
db-log-{env}Logs — ELMAH, job execution logs
db-eshars-{env}-auditAudit trail

Resources Per Environment

graph TD
    RG["Resource Group<br/>eshars-{env}-rg"]
    RG --> ASP["App Service Plan"]
    RG --> WA["Web App<br/>wa-eshars-{env}"]
    RG --> SLOT["Staging Slot"]
    RG --> FA["Function App<br/>func-eshars-{env}"]
    RG --> SQL["SQL Server"]
    SQL --> DB1["Core DB"]
    SQL --> DB2["Log DB"]
    SQL --> DB3["Audit DB"]
    RG --> SA["Storage Account<br/>saeshars{env}"]
    SA --> C1["6 blob containers"]
    RG --> REDIS["Redis Cache"]
    RG --> AI["Application Insights"]
    AI --> LAW["Log Analytics"]
    RG --> KV["Key Vault"]
    KV --> SEC["Secrets"]
    WA --> |"managed identity"| KV
    FA --> |"managed identity"| KV
    SLOT --> |"managed identity"| KV

Module Details

app-insights

Creates a Log Analytics Workspace and Application Insights resource connected to it.

OutputDescription
instrumentation_keyApp Insights instrumentation key (sensitive)
connection_stringApp Insights connection string (sensitive)
workspace_idLog Analytics workspace resource ID

sql-database

Creates 3 SQL databases (Core, Log, Audit). Supports two modes:

  • Elastic pool (elastic_pool_enabled = true) — all 3 DBs share a pool. Used by dev/qa/training.
  • Standalone (elastic_pool_enabled = false) — each DB gets its own SKU. Used by uat/regression/prod.

storage

Creates a Storage Account with 6 blob containers:

ContainerPurpose
defaultGeneral storage
certificate-attachmentsClinician certificate files
provider-rx-sign-detailsProvider prescription signatures
eu-universityEU university documents
iepIEP documents
log-filesApplication log files (auto-deleted after 90 days)

redis

Redis Cache for session state and output cache. TLS 1.2 enforced, non-SSL port disabled.

web-app

Windows App Service running .NET Framework 4.8. Includes:

  • Staging slot for zero-downtime swap deployments
  • System-assigned managed identity with Key Vault Secrets User role
  • App settings include Key Vault name, App Insights keys, timezone (CST)
  • HTTPS-only, TLS 1.2, FTP disabled

function-app

Windows Function App on the same App Service Plan. Same managed identity + Key Vault pattern as the web app.

key-vault-secrets

Seeds Key Vault with operational secrets:

SecretSource
ESHARSCore DB connection string (constructed from SQL Server FQDN)
ESHARSLOGSLog DB connection string
ESHARSAUDITAudit DB connection string
RedisConnectionStringRedis module output
AzureBlobStorageConnectionStringStorage module output
applicationInsightKeyApp Insights module output
SendGridApiKeyPlaceholder — update post-provisioning
EncryptionKeyPlaceholder — update post-provisioning
EncryptionkeyBytePlaceholder — update post-provisioning
masterPasswordPlaceholder — update post-provisioning
MvcReportViewerUsernamePlaceholder — update post-provisioning
MvcReportViewerPasswordPlaceholder — update post-provisioning
EchoSignAppKeyPlaceholder — update post-provisioning
HandShakingKeyPlaceholder — update post-provisioning

SKU Map

Resourcedev/qa/traininguatregressionstagingprod
App Service PlanB1 / S1 / S1S1S1S2P1v3
SQLElasticPool (50 eDTU)P1P1P1P1
RedisBasic/C0Standard/C0Basic/C0Standard/C0Standard/C1
Storage replicationLRSLRSLRSLRSGRS
Backup retention7 days7 days7 days7 days35 days

Deploying a New Environment

Prerequisites

  • OpenTofu >= 1.6.0
  • Azure CLI authenticated (az login)
  • Backend storage account for tfstate (configured via -backend-config)

Steps

Terminal window
cd eshars-ops/develop/infrastructure
# 1. Initialize (first time or after module changes)
tofu init -backend-config=backends/{env}.hcl
# 2. Set sensitive variables
export TF_VAR_sql_admin_login="..."
export TF_VAR_sql_admin_password="..."
export TF_VAR_quartz_connection_string="..."
export TF_VAR_logs_connection_string="..."
# 3. Plan
tofu plan -var-file=environments/{env}.tfvars -out=tfplan
# 4. Review the plan carefully, then apply
tofu apply tfplan
# 5. Post-provisioning: update placeholder secrets in Key Vault
az keyvault secret set --vault-name eshars-{env}-kv \
--name SendGridApiKey --value "actual-key-here"
# ... repeat for all placeholder secrets

Adding a New Environment

  1. Create environments/{env}.tfvars with appropriate SKUs
  2. Add the environment name to the validation block in variables.tf
  3. Create a backend config backends/{env}.hcl pointing to the tfstate container
  4. Follow the deploy steps above

Sensitive Data

All sensitive values are passed via TF_VAR_ environment variables — never stored in .tfvars files:

VariableDescription
TF_VAR_sql_admin_loginSQL Server admin username
TF_VAR_sql_admin_passwordSQL Server admin password
TF_VAR_quartz_connection_stringCore DB connection string (for monitoring module)
TF_VAR_logs_connection_stringLog DB connection string (for monitoring module)
TF_VAR_teams_webhook_urlTeams webhook for alert notifications