Skip to main content

Terraform Multi-Stage Pipelines

Pipeline Detail

Inspired by David's work here: https://github.com/flcdrg/azure-pipelines-each/

Repo Structure

Terraform modules go in src/

Terraform Repo Structure

Pipelines

variables-production.yml
variables:
environment: 'production'
terraform_version: '1.6.3'
terraform_bucket_name: 'acme-corp-production-terraform'
aws_service_connection: 'AWS - Acme Corp'
aws_region: 'ap-southeast-2'
azure-pipelines.yml
parameters:
- name: environments
type: object
default:
- name: development
- name: production

trigger: none

stages:
- stage: Build
displayName: Build lambda webhook
dependsOn: []
jobs:
- template: terraform-build.yml

- ${{ each environment in parameters.environments }}:
- stage: TerraformPlan_${{ environment.name }}
displayName: Plan ${{ environment.name }}
dependsOn: Build
variables:
- template: variables-${{ environment.name }}.yml
- group: infrastructure-variables-${{ environment.name }}
jobs:
- template: terraform-plan.yml
parameters:
environmentName: ${{ environment.name }}

- stage: TerraformApply_${{ environment.name }}
displayName: Apply ${{ environment.name }}
dependsOn: TerraformPlan_${{ environment.name }}
variables:
- template: variables-${{ environment.name }}.yml
- group: infrastructure-variables-${{ environment.name }}
jobs:
- template: terraform-apply.yml
parameters:
environmentName: ${{ environment.name }}
terraform-build.yml
jobs:
- job: build
pool:
vmImage: ubuntu-latest
steps:
- task: NodeTool@0
displayName: Setup Node
inputs:
versionSpec: '19.4.x'

- task: Bash@3
displayName: Install dependencies
inputs:
targetType: inline
workingDirectory: src/webhooks
script: |
npm install

- task: Bash@3
displayName: Build webhook
inputs:
targetType: inline
workingDirectory: src/webhooks
script: |
npm run build
terraform-plan.yml
parameters:
- name: environmentName
type: string

jobs:
- job: plan
variables:
- template: variables-${{ parameters.environmentName }}.yml
- group: infrastructure-variables-${{ parameters.environmentName }}
pool:
vmImage: ubuntu-latest
steps:
- task: JasonBJohnson.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-installer.TerraformInstaller@1
displayName: install terraform $(terraform_version)
inputs:
terraform_version: $(terraform_version)

- task: JasonBJohnson.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@1
displayName: terraform init
inputs:
command: init
workingDirectory: '$(System.DefaultWorkingDirectory)/src'
backendType: aws
backendServiceAws: ${{ variables.aws_service_connection }}
backendAWSBucket: $(terraform_bucket_name)
backendAWSKey: 'terraform.tfstate'

- task: replacetokens@5
inputs:
targetFiles: "environments/${{ parameters.environmentName }}.tfvars"
encoding: 'auto'
tokenPattern: 'octopus'
writeBOM: false
actionOnMissing: 'warn'
keepToken: false
actionOnNoFiles: 'continue'
enableTransforms: false
enableRecursion: false
useLegacyPattern: false
enableTelemetry: true

- task: Bash@3
displayName: Install dependencies
inputs:
targetType: inline
workingDirectory: $(System.DefaultWorkingDirectory)
script: |
mkdir terraform_plan

- task: JasonBJohnson.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@1
displayName: terraform plan
inputs:
command: plan
commandOptions: "--var-file=$(System.DefaultWorkingDirectory)/environments/${{ parameters.environmentName }}.tfvars -out=${{ parameters.environmentName }}_plan.tfplan"
publishPlanResults: "${{ parameters.environmentName }}_plan"
workingDirectory: '$(System.DefaultWorkingDirectory)/src'
providerServiceAws: ${{ variables.aws_service_connection }}
providerAwsRegion: $(aws_region)

- task: PublishPipelineArtifact@1
displayName: Publish Plan artifact
inputs:
targetPath: '$(System.DefaultWorkingDirectory)/src'
artifactName: "${{ parameters.environmentName }}_terraform_plan"
publishLocation: 'pipeline'
terraform-apply.yml
parameters:
- name: environmentName
type: string

jobs:
- deployment: Apply
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
variables:
- template: variables-${{ parameters.environmentName }}.yml
pool:
vmImage: ubuntu-latest
environment: ${{ parameters.environmentName }}
strategy:
runOnce:
deploy:
steps:
- task: JasonBJohnson.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-installer.TerraformInstaller@1
displayName: Install terraform $(terraform_version)
inputs:
terraform_version: $(terraform_version)

- task: DownloadPipelineArtifact@2
inputs:
artifactName: "${{ parameters.environmentName }}_terraform_plan"
path: "${{ parameters.environmentName }}_terraform_plan"

- task: Bash@3
displayName: List files
inputs:
targetType: inline
workingDirectory: $(System.DefaultWorkingDirectory)
script: |
rm -rf ${{ parameters.environmentName }}_terraform_plan/.terraform/
find .

- task: JasonBJohnson.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@1
displayName: terraform init
inputs:
command: init
workingDirectory: "${{ parameters.environmentName }}_terraform_plan"
backendType: aws
backendServiceAws: ${{ variables.aws_service_connection }}
backendAWSBucket: $(terraform_bucket_name)
backendAWSKey: 'terraform.tfstate'

- task: JasonBJohnson.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@1
displayName: terraform apply
inputs:
command: apply
workingDirectory: "${{ parameters.environmentName }}_terraform_plan"
commandOptions: "${{ parameters.environmentName }}_plan.tfplan"
providerServiceAws: ${{ variables.aws_service_connection }}
providerAwsRegion: $(aws_region)