3.1.5.3. IaC in CI/CD: Plan-and-Approve, Testing, and Self-Service
3.1.5.3. IaC in CI/CD: Plan-and-Approve, Testing, and Self-Service
Having IaC tools is the first step; integrating them safely into CI/CD pipelines — with plan-and-approve workflows, testing, and self-service — is where the real value emerges.
IaC integration with CI/CD pipelines requires specific patterns for safety and reliability. Terraform production pipelines should never use -auto-approve — a plan showing "destroy 3 resources" would execute immediately without review. The plan-and-approve workflow saves the plan file, posts output for review, and applies only the approved plan. az deployment group what-if provides similar pre-deployment validation for Bicep, revealing drift between template desired state and actual Azure state. Azure Deployment Environments enable developer self-service: platform teams define IaC templates in a catalog, developers create environments without needing ARM/Bicep knowledge or subscription permissions. Bicep testing layers include linting (syntax), parameter validation (module inputs/outputs without deployment), what-if (preview against real subscription), and integration testing (deploy to test subscription and verify). Parameter validation is the cheapest and fastest layer.
Content for Automation of testing - see flashcards and questions for this subsection.
The Terraform plan-apply pattern in CI/CD separates intent from execution. terraform plan -out=tfplan generates a plan file showing exactly what will change. The plan output can be posted as a PR comment for team review, attached as a pipeline artifact, or sent to a Slack channel for visibility. terraform apply tfplan then executes only the approved changes — no surprise resource deletions.
For Bicep, az deployment group what-if serves the same preview purpose. The what-if output categorizes changes as Create, Modify, Delete, or No Change, making it easy to spot unintended modifications. Drift detection — where manual portal changes conflict with IaC definitions — shows up as modifications the deployment would make to "correct" the drift.
Azure Deployment Environments automate environment provisioning for developer self-service. Platform engineers define environment templates (Bicep or Terraform) in a GitHub catalog. Developers create environments through the developer portal or CLI without needing Azure subscription access or IaC knowledge. Environments can auto-expire after a configurable period, preventing cost accumulation from abandoned test environments.
Bicep testing follows a cost-efficiency pyramid: linting (free, instant), parameter validation (free, seconds), what-if (requires subscription, seconds), integration testing (requires deployment, minutes). Run cheaper tests first; only invest in real deployments for critical modules. PSRule for Azure adds policy-as-code validation that catches Azure best practice violations before deployment.
Environment-specific variable management in IaC pipelines uses parameter files per environment. Bicep: main.bicepparam with environment-specific values. Terraform: dev.tfvars, staging.tfvars, prod.tfvars. The pipeline template selects the appropriate file based on the target environment, ensuring infrastructure configuration differs only in explicitly declared parameters — not in template logic.
Policy-as-code with Azure Policy validates IaC outputs against organizational standards. Deny policies prevent non-compliant resources (public storage accounts, unencrypted databases). Audit policies flag non-compliance for remediation. DeployIfNotExists policies automatically add missing components (diagnostic settings, backup configurations). When combined with IaC pipelines, policy-as-code creates a dual enforcement model: IaC defines what should exist, policy validates it meets standards.
Terraform workspaces within a single backend allow managing multiple environments with the same configuration but different state. However, workspace-per-environment creates coupling: a template change tested in dev's workspace uses the same configuration that prod's workspace will consume. Many teams prefer directory-per-environment for stronger isolation, accepting the duplication trade-off.
Infrastructure testing follows a similar pyramid to application testing. Linting and validation are fast, cheap, and catch syntax errors. What-if comparisons are medium-cost and catch logical errors. Full deployment testing is expensive but catches real-world provisioning failures that static analysis cannot detect.
Destroy protection prevents accidental infrastructure deletion. Terraform's prevent_destroy lifecycle rule blocks terraform destroy on critical resources. Bicep deployments in complete mode delete resources not in the template — always use incremental mode for production and restrict complete mode to dedicated cleanup pipelines.