Skip to content

Commit fc8a431

Browse files
authored
Feat: infra pipeline tag trigger (#310)
2 parents d697492 + eca6eef commit fc8a431

File tree

3 files changed

+133
-23
lines changed

3 files changed

+133
-23
lines changed

terraform/pipeline/azure-pipelines.yml

Lines changed: 108 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,52 @@
11
trigger:
2-
# automatically runs on pull requests
3-
# https://docs.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#pr-triggers
42
branches:
53
include:
64
- dev
75
- test
86
- prod
7+
tags:
8+
include:
9+
- 20??.??.?*-rc?*
10+
- 20??.??.?*
911
# only run for changes to Terraform files
1012
paths:
1113
include:
1214
- terraform/*
15+
16+
pr:
17+
branches:
18+
include:
19+
- "*"
20+
paths:
21+
include:
22+
- terraform/*
23+
24+
pool:
25+
vmImage: ubuntu-latest
26+
1327
stages:
14-
- stage: terraform
15-
pool:
16-
vmImage: ubuntu-latest
28+
- stage: TerraformPlan
1729
jobs:
18-
- job: terraform
30+
- job: Plan
1931
variables:
2032
- name: OTHER_SOURCE
2133
value: $[variables['System.PullRequest.SourceBranch']]
2234
- name: INDIVIDUAL_SOURCE
2335
value: $[variables['Build.SourceBranchName']]
36+
- name: IS_TAG
37+
value: $[startsWith(variables['Build.SourceBranch'], 'refs/tags/')]
2438
- name: TARGET
2539
value: $[variables['System.PullRequest.TargetBranch']]
2640
steps:
2741
# set the workspace variable at runtime (rather than build time) so that all the necessary variables are available, and we can use Python
2842
# https://learn.microsoft.com/en-us/azure/devops/pipelines/process/set-variables-scripts?view=azure-devops&tabs=bash#about-tasksetvariable
2943
- bash: |
3044
WORKSPACE=$(python terraform/pipeline/workspace.py)
31-
echo "##vso[task.setvariable variable=workspace]$WORKSPACE"
45+
echo "##vso[task.setvariable variable=workspace;isOutput=true]$WORKSPACE"
46+
47+
TAG_TYPE=$(python terraform/pipeline/tag.py)
48+
echo "##vso[task.setvariable variable=tag_type;isOutput=true]$TAG_TYPE"
49+
name: setvars
3250
displayName: Determine deployment environment
3351
env:
3452
REASON: $(Build.Reason)
@@ -59,7 +77,7 @@ stages:
5977
provider: azurerm
6078
command: custom
6179
customCommand: workspace
62-
commandOptions: select $(workspace)
80+
commandOptions: select $(setvars.workspace)
6381
workingDirectory: "$(System.DefaultWorkingDirectory)/terraform"
6482
# service connection
6583
environmentServiceNameAzureRM: deployer
@@ -70,21 +88,88 @@ stages:
7088
command: plan
7189
# wait for lock to be released, in case being used by another pipeline run
7290
# https://discuss.hashicorp.com/t/terraform-plan-wait-for-lock-to-be-released/6870/2
73-
commandOptions: -input=false -lock-timeout=5m
74-
workingDirectory: "$(System.DefaultWorkingDirectory)/terraform"
75-
# service connection
76-
environmentServiceNameAzureRM: deployer
77-
# the plan is done as part of the apply (below), so don't bother doing it twice
78-
condition: notIn(variables['Build.SourceBranchName'], 'dev', 'test', 'prod')
79-
- task: TerraformTaskV3@3
80-
displayName: Terraform apply
81-
inputs:
82-
provider: azurerm
83-
command: apply
84-
# (ditto the lock comment above)
85-
commandOptions: -input=false -lock-timeout=5m
91+
commandOptions: -input=false -lock-timeout=5m -out=$(Build.ArtifactStagingDirectory)/tfplan
8692
workingDirectory: "$(System.DefaultWorkingDirectory)/terraform"
8793
# service connection
8894
environmentServiceNameAzureRM: deployer
89-
# only run on certain branches
90-
condition: in(variables['Build.SourceBranchName'], 'dev', 'test', 'prod')
95+
# need to publish the tfplan to used by next stage if it's going to run
96+
- publish: "$(Build.ArtifactStagingDirectory)"
97+
displayName: "Publish tfplan for use in TerraformApply"
98+
artifact: savedPlan
99+
condition: |
100+
or(
101+
in(variables['Build.SourceBranchName'], 'dev', 'test', 'prod'),
102+
eq(variables['setvars.tag_type'], 'test'),
103+
eq(variables['setvars.tag_type'], 'prod')
104+
)
105+
- stage: TerraformApply
106+
dependsOn: TerraformPlan
107+
variables:
108+
- name: workspace
109+
value: $[ dependencies.TerraformPlan.outputs['Plan.setvars.workspace'] ]
110+
- name: tag_type
111+
value: $[ dependencies.TerraformPlan.outputs['Plan.setvars.tag_type'] ]
112+
# only run on dev, test, or prod branches OR if it's a tag for test or prod
113+
condition: |
114+
or(
115+
in(variables['Build.SourceBranchName'], 'dev', 'test', 'prod'),
116+
eq(variables['tag_type'], 'test'),
117+
eq(variables['tag_type'], 'prod')
118+
)
119+
jobs:
120+
- deployment: Apply
121+
condition: succeeded()
122+
environment: Approval
123+
variables:
124+
- name: workspace
125+
value: $[ stageDependencies.TerraformPlan.Plan.outputs['setvars.workspace'] ]
126+
- name: tag_type
127+
value: $[ stageDependencies.TerraformPlan.Plan.outputs['setvars.tag_type'] ]
128+
strategy:
129+
runOnce:
130+
deploy:
131+
steps:
132+
- checkout: self
133+
- download: current
134+
displayName: "Download plan file published from TerraformPlan"
135+
artifact: savedPlan
136+
- task: TerraformInstaller@0
137+
displayName: Install Terraform
138+
inputs:
139+
terraformVersion: 1.3.1
140+
# https://github.com/microsoft/azure-pipelines-terraform/tree/main/Tasks/TerraformTask/TerraformTaskV3#readme
141+
- task: TerraformTaskV3@3
142+
displayName: Terraform init
143+
inputs:
144+
provider: azurerm
145+
command: init
146+
workingDirectory: "$(System.DefaultWorkingDirectory)/terraform"
147+
# https://developer.hashicorp.com/terraform/tutorials/automation/automate-terraform#automated-terraform-cli-workflow
148+
commandOptions: -input=false
149+
# service connection
150+
backendServiceArm: deployer
151+
# needs to match main.tf
152+
backendAzureRmResourceGroupName: courtesy-cards-eligibility-terraform
153+
backendAzureRmStorageAccountName: courtesycardsterraform
154+
backendAzureRmContainerName: tfstate
155+
backendAzureRmKey: terraform.tfstate
156+
- task: TerraformTaskV3@3
157+
displayName: Select environment
158+
inputs:
159+
provider: azurerm
160+
command: custom
161+
customCommand: workspace
162+
commandOptions: select $(workspace)
163+
workingDirectory: "$(System.DefaultWorkingDirectory)/terraform"
164+
# service connection
165+
environmentServiceNameAzureRM: deployer
166+
- task: TerraformTaskV3@3
167+
displayName: Terraform apply
168+
inputs:
169+
provider: azurerm
170+
command: apply
171+
# (ditto the lock comment above)
172+
commandOptions: -input=false -lock-timeout=5m $(Pipeline.Workspace)/savedPlan/tfplan
173+
workingDirectory: "$(System.DefaultWorkingDirectory)/terraform"
174+
# service connection
175+
environmentServiceNameAzureRM: deployer

terraform/pipeline/tag.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import os
2+
import re
3+
4+
REASON = os.environ["REASON"]
5+
# use variable corresponding to tag triggers
6+
SOURCE = os.environ["INDIVIDUAL_SOURCE"]
7+
IS_TAG = os.environ["IS_TAG"].lower() == "true"
8+
9+
if REASON == "IndividualCI" and IS_TAG:
10+
if re.fullmatch(r"20\d\d.\d\d.\d+-rc\d+", SOURCE):
11+
tag_type = "test"
12+
elif re.fullmatch(r"20\d\d.\d\d.\d+", SOURCE):
13+
tag_type = "prod"
14+
else:
15+
tag_type = None
16+
else:
17+
tag_type = None
18+
19+
print(tag_type)

terraform/pipeline/workspace.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import os
2+
import re
23
import sys
34

45
REASON = os.environ["REASON"]
56
# the name of the variable that Azure Pipelines uses for the source branch depends on the type of run, so need to check both
67
SOURCE = os.environ.get("OTHER_SOURCE") or os.environ["INDIVIDUAL_SOURCE"]
78
TARGET = os.environ["TARGET"]
9+
IS_TAG = os.environ["IS_TAG"].lower() == "true"
810

911
# the branches that correspond to environments
1012
ENV_BRANCHES = ["dev", "test", "prod"]
@@ -15,6 +17,10 @@
1517
elif REASON in ["IndividualCI", "Manual"] and SOURCE in ENV_BRANCHES:
1618
# it's being run on one of the environment branches, so use that
1719
environment = SOURCE
20+
elif REASON in ["IndividualCI"] and IS_TAG and re.fullmatch(r"20\d\d.\d\d.\d+-rc\d+", SOURCE):
21+
environment = "test"
22+
elif REASON in ["IndividualCI"] and IS_TAG and re.fullmatch(r"20\d\d.\d\d.\d+", SOURCE):
23+
environment = "prod"
1824
else:
1925
# default to running against dev
2026
environment = "dev"

0 commit comments

Comments
 (0)