XMPro Documentation PR Preview Infrastructure

This Terraform module creates temporary Azure infrastructure for previewing XMPro documentation pull requests. It provides isolated environments for each PR to enable reviewers to see documentation changes before they're merged.

Overview

When a PR is created against the documentation, this module:

  1. Creates isolated Azure resources - Each PR gets its own resource group and storage account
  2. Deploys static website hosting - Azure Storage Account with static website features enabled
  3. Uploads DocFX documentation - Pipeline builds and uploads the documentation site
  4. Provides preview URL - Public URL for reviewers to preview documentation changes
  5. Automatic cleanup - Resources are destroyed when the PR is closed

Architecture

Resource Layout

Resource Group (rg-xmpro-docs-pr-preview-{pr_number})
└── Storage Account (xmprodocpr{pr_number})
    └── Static Website ($web container)
        └── DocFX documentation site

State Management

Azure Storage Backend
├── Container: terraform-state-docs
├── State Files:
│   ├── docs-pr-1234.tfstate    # PR #1234 state
│   ├── docs-pr-1235.tfstate    # PR #1235 state
│   └── docs-pr-1236.tfstate    # PR #1236 state
└── Concurrent Build Support: ✓ No locking conflicts

Key Design Principles:

  • Complete Isolation: Each PR gets separate resource group and storage account
  • Concurrent Safety: PR-specific state files prevent locking conflicts
  • Cost Effective: Uses cheapest Azure storage options
  • Automatic Cleanup: Resources destroyed when PR closes

Usage

This module is automatically used by Azure DevOps pipelines when PRs are created. It's not intended for manual deployment.

Automated Pipeline Integration

PR Creation/Update:

  • Pipeline: docs/docs-build-pr.yml
  • Trigger: Pull request to main or release-* branches
  • Actions: Build documentation → Deploy preview → Comment on PR with URL

PR Closure:

  • Pipeline: docs/docs-cleanup-pr.yml
  • Trigger: Pull request closed
  • Actions: Destroy Azure resources → Remove state file

State Management Details

Backend Configuration

The module uses a direct initialization process with PR-specific state files:

# main.tf includes backend configuration
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.0"
    }
  }
  
  backend "azurerm" {
    # Backend configuration provided via -backend-config during init
  }
}

Initialization Process:

# Direct initialization with PR-specific state file
terraform init \
  -backend-config="storage_account_name=$(storageaccount)" \
  -backend-config="container_name=terraform-state-docs" \
  -backend-config="key=docs-pr-${PR_NUMBER}.tfstate"

Why This Approach?

Problem: Multiple PRs building simultaneously would conflict on the same state file Solution: Each PR gets its own state file: docs-pr-{pr_number}.tfstate Benefits:

  • No state locking conflicts between concurrent PR builds
  • Isolated state management per PR
  • Direct backend configuration prevents state file mismatches
  • Consistent state management across pipeline steps

Pipeline Workflow

1. PR Build Pipeline (docs/docs-build-pr.yml)

Triggers: Pull request to main or release-* branches

Stages:

graph LR
    A[Build DocFX] --> B[Deploy Preview]
    B --> C[Upload Docs]
    C --> D[Comment on PR]

Detailed Steps:

  1. Build Stage: DocFX builds documentation, creates _site artifact
  2. Deploy Stage:
    • Initialize Terraform directly with PR-specific state file
    • Apply Terraform to create/update Azure resources
    • Sync documentation to storage account using az storage blob sync
    • Display preview URL with changed documentation pages

2. PR Cleanup Pipeline (docs/docs-cleanup-pr.yml)

Triggers: Pull request closed

Actions:

  1. Initialize Terraform with PR-specific state file
  2. Run terraform destroy to remove all Azure resources
  3. State file is automatically removed during destroy

Variables

Name Description Type Default Notes
pr_number Pull request number string Required Used in resource naming
build_id Azure DevOps build ID string Required Used for tagging
location Azure region for resources string eastus Where resources are created
resource_group_name Base name of resource group string rg-xmpro-docs-pr-preview PR number is appended

Outputs

Name Description Usage
docs_preview_url Static website URL Posted in PR comments for reviewers
storage_account_name Storage account name Used by pipeline to upload files
storage_account_key Storage account access key Used by pipeline for authentication
resource_group_name Full resource group name Used for resource management

Azure Resources Created

Per PR Resources

# Resource Group
rg-xmpro-docs-pr-preview-{pr_number}

# Storage Account (with static website)
xmprodocpr{pr_number}
├── $web container (static website files)
├── Primary web endpoint (public URL)
└── Access keys (for upload authentication)

Resource Naming Rules

  • Storage Account: xmprodocpr{pr_number} (max 24 chars, lowercase only)
  • Resource Group: rg-xmpro-docs-pr-preview-{pr_number}
  • State File: docs-pr-{pr_number}.tfstate

Dependencies

Azure DevOps Variable Groups

  • terraform-devtestplan-state: Contains backend configuration
    • service_name: Azure service connection name
    • key_vault_name: Key Vault containing secrets
    • storageaccount: Terraform state storage account
    • container: Container name (overridden to terraform-state-docs)

Azure Key Vault Secrets

Retrieved automatically via terraform-devtestplan-state group:

  • sas-token: Storage account SAS token for state backend
  • az-client-id: Service principal client ID
  • az-client-secret: Service principal secret
  • az-subscription: Azure subscription ID
  • az-tenant: Azure tenant ID

Cost Considerations

Resource Costs Per PR:

  • Storage Account: ~$0.05/month (Standard LRS, minimal usage)
  • Static Website: No additional cost
  • Resource Group: No cost

Cost Management:

  • Resources are automatically cleaned up when PRs are closed
  • Maximum lifetime: Duration of PR (typically days, not months)
  • Estimated cost per PR: < $0.50 USD total

Cost Optimization:

  • Uses cheapest storage replication (LRS)
  • No compute resources (static hosting only)
  • Automatic cleanup prevents cost accumulation

Security

Data Protection

  • Static websites are publicly accessible (by design for PR reviews)
  • HTTPS enforced on all storage account endpoints
  • No sensitive data should be included in documentation
  • Access keys marked sensitive in Terraform outputs

Authentication & Authorization

  • Service Principal authentication for Terraform operations
  • Azure Key Vault for secret management
  • RBAC permissions controlled via Azure DevOps service connections
  • State file access controlled via storage account permissions

Security Best Practices

  • Documentation contains only public information
  • No API keys, passwords, or internal URLs in docs
  • Preview URLs are time-limited (PR lifetime only)
  • Regular cleanup prevents data persistence

Cleanup Process

Automatic Cleanup Triggers

Resources are automatically cleaned up when:

  • PR is closed (merged or abandoned)
  • PR cleanup pipeline runs successfully

Manual Cleanup (if needed)

# If automatic cleanup fails, manually run:
cd docs/terraform-pr
terraform init \
  -backend-config="storage_account_name=STORAGE_ACCOUNT" \
  -backend-config="container_name=terraform-state-docs" \
  -backend-config="key=docs-pr-{PR_NUMBER}.tfstate" \
  -reconfigure

terraform destroy \
  -var="pr_number={PR_NUMBER}" \
  -var="build_id=manual-cleanup" \
  -auto-approve

Cleanup Verification

  • Check Azure Portal: Resource group should be deleted
  • Check state storage: docs-pr-{pr_number}.tfstate should be removed
  • No ongoing costs should appear in Azure billing

Troubleshooting

Common Issues

1. Preview URL Not Working

Symptoms: 404 error or blank page Solutions:

  • Verify storage account was created: az storage account show -n xmprodocpr{pr_number}
  • Check static website enabled: Look for primary_web_endpoint in outputs
  • Confirm files uploaded: Check $web container contents
  • Wait 2-3 minutes for DNS propagation

2. State Lock Issues

Symptoms: "Backend configuration changed" or lock errors Solutions:

  • Concurrent builds: PR-specific state files prevent most locking conflicts
  • Stuck locks: Locks auto-expire after 15 minutes
  • Manual unlock: Use terraform force-unlock with lock ID
  • State mismatch: Ensure backend configuration includes proper azurerm block

3. Storage Account Name Conflicts

Symptoms: "Storage account name already exists" Solutions:

  • Check if previous cleanup failed: Look for existing xmprodocpr{pr_number}
  • Manual cleanup of orphaned resources
  • Storage account names are globally unique across Azure

4. Pipeline Authentication Failures

Symptoms: "Unauthorized" or "Forbidden" errors Solutions:

  • Verify terraform-devtestplan-state variable group exists
  • Check Azure Key Vault permissions for service principal
  • Confirm service connection is valid and not expired

5. Terraform Init Failures

Symptoms: Backend initialization errors Solutions:

  • Verify terraform-state-docs container exists in storage account
  • Check SAS token permissions and expiration
  • Ensure storage account allows Terraform operations
  • Verify main.tf includes proper backend "azurerm" block

Debug Information

Useful Commands

# Check storage account details
az storage account show -n xmprodocpr{pr_number} -g rg-xmpro-docs-pr-preview-{pr_number}

# List state files
az storage blob list --container-name terraform-state-docs --account-name {state_storage_account}

# Check static website configuration
az storage blob service-properties show --account-name xmprodocpr{pr_number}

Pipeline Variables to Check

  • System.PullRequest.PullRequestId: Should contain PR number
  • storageaccount: Backend storage account name
  • sas-token: Should not be empty or expired
  • key_vault_name: Key Vault containing secrets

Getting Help

If issues persist:

  1. Check Azure DevOps pipeline logs for detailed error messages
  2. Verify Azure Portal for resource creation status
  3. Check variable group values in Azure DevOps
  4. Contact platform team with PR number and error details

Maintenance

Regular Tasks

  • Monitor costs: Review Azure billing for unexpected charges
  • Clean up orphaned resources: Check for resources not cleaned up automatically
  • Update documentation: Keep README current with any pipeline changes
  • Review security: Ensure no sensitive data in documentation

Updates Required When

  • Azure provider version changes: Update required_providers in main.tf
  • Pipeline template changes: Update deployment template if process changes
  • Variable group changes: Update documentation if new secrets added
  • Naming conventions change: Update resource naming rules

This module provides a robust, cost-effective solution for documentation PR previews with proper isolation and automatic cleanup.