Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: infra pipeline tag trigger #310

Merged
merged 4 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 108 additions & 23 deletions terraform/pipeline/azure-pipelines.yml
Original file line number Diff line number Diff line change
@@ -1,34 +1,52 @@
trigger:
# automatically runs on pull requests
# https://docs.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#pr-triggers
branches:
include:
- dev
- test
- prod
tags:
include:
- 20??.??.?*-rc?*
- 20??.??.?*
# only run for changes to Terraform files
paths:
include:
- terraform/*

pr:
branches:
include:
- "*"
paths:
include:
- terraform/*

pool:
vmImage: ubuntu-latest

stages:
- stage: terraform
pool:
vmImage: ubuntu-latest
- stage: TerraformPlan
jobs:
- job: terraform
- job: Plan
variables:
- name: OTHER_SOURCE
value: $[variables['System.PullRequest.SourceBranch']]
- name: INDIVIDUAL_SOURCE
value: $[variables['Build.SourceBranchName']]
- name: IS_TAG
value: $[startsWith(variables['Build.SourceBranch'], 'refs/tags/')]
- name: TARGET
value: $[variables['System.PullRequest.TargetBranch']]
steps:
# set the workspace variable at runtime (rather than build time) so that all the necessary variables are available, and we can use Python
# https://learn.microsoft.com/en-us/azure/devops/pipelines/process/set-variables-scripts?view=azure-devops&tabs=bash#about-tasksetvariable
- bash: |
WORKSPACE=$(python terraform/pipeline/workspace.py)
echo "##vso[task.setvariable variable=workspace]$WORKSPACE"
echo "##vso[task.setvariable variable=workspace;isOutput=true]$WORKSPACE"

TAG_TYPE=$(python terraform/pipeline/tag.py)
echo "##vso[task.setvariable variable=tag_type;isOutput=true]$TAG_TYPE"
name: setvars
displayName: Determine deployment environment
env:
REASON: $(Build.Reason)
Expand Down Expand Up @@ -59,7 +77,7 @@ stages:
provider: azurerm
command: custom
customCommand: workspace
commandOptions: select $(workspace)
commandOptions: select $(setvars.workspace)
workingDirectory: "$(System.DefaultWorkingDirectory)/terraform"
# service connection
environmentServiceNameAzureRM: deployer
Expand All @@ -70,21 +88,88 @@ stages:
command: plan
# wait for lock to be released, in case being used by another pipeline run
# https://discuss.hashicorp.com/t/terraform-plan-wait-for-lock-to-be-released/6870/2
commandOptions: -input=false -lock-timeout=5m
workingDirectory: "$(System.DefaultWorkingDirectory)/terraform"
# service connection
environmentServiceNameAzureRM: deployer
# the plan is done as part of the apply (below), so don't bother doing it twice
condition: notIn(variables['Build.SourceBranchName'], 'dev', 'test', 'prod')
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We no longer need a condition on the terraform plan step because it should always run.

- task: TerraformTaskV3@3
displayName: Terraform apply
inputs:
provider: azurerm
command: apply
# (ditto the lock comment above)
commandOptions: -input=false -lock-timeout=5m
commandOptions: -input=false -lock-timeout=5m -out=$(Build.ArtifactStagingDirectory)/tfplan
workingDirectory: "$(System.DefaultWorkingDirectory)/terraform"
# service connection
environmentServiceNameAzureRM: deployer
# only run on certain branches
condition: in(variables['Build.SourceBranchName'], 'dev', 'test', 'prod')
# need to publish the tfplan to used by next stage if it's going to run
- publish: "$(Build.ArtifactStagingDirectory)"
displayName: "Publish tfplan for use in TerraformApply"
artifact: savedPlan
condition: |
or(
in(variables['Build.SourceBranchName'], 'dev', 'test', 'prod'),
eq(variables['setvars.tag_type'], 'test'),
eq(variables['setvars.tag_type'], 'prod')
)
- stage: TerraformApply
dependsOn: TerraformPlan
variables:
- name: workspace
value: $[ dependencies.TerraformPlan.outputs['Plan.setvars.workspace'] ]
- name: tag_type
value: $[ dependencies.TerraformPlan.outputs['Plan.setvars.tag_type'] ]
# only run on dev, test, or prod branches OR if it's a tag for test or prod
condition: |
or(
in(variables['Build.SourceBranchName'], 'dev', 'test', 'prod'),
eq(variables['tag_type'], 'test'),
eq(variables['tag_type'], 'prod')
)
jobs:
- deployment: Apply
condition: succeeded()
environment: Approval
variables:
- name: workspace
value: $[ stageDependencies.TerraformPlan.Plan.outputs['setvars.workspace'] ]
- name: tag_type
value: $[ stageDependencies.TerraformPlan.Plan.outputs['setvars.tag_type'] ]
strategy:
runOnce:
deploy:
steps:
- checkout: self
- download: current
displayName: "Download plan file published from TerraformPlan"
artifact: savedPlan
- task: TerraformInstaller@0
displayName: Install Terraform
inputs:
terraformVersion: 1.3.1
thekaveman marked this conversation as resolved.
Show resolved Hide resolved
# https://github.com/microsoft/azure-pipelines-terraform/tree/main/Tasks/TerraformTask/TerraformTaskV3#readme
- task: TerraformTaskV3@3
displayName: Terraform init
inputs:
provider: azurerm
command: init
workingDirectory: "$(System.DefaultWorkingDirectory)/terraform"
# https://developer.hashicorp.com/terraform/tutorials/automation/automate-terraform#automated-terraform-cli-workflow
commandOptions: -input=false
# service connection
backendServiceArm: deployer
# needs to match main.tf
backendAzureRmResourceGroupName: courtesy-cards-eligibility-terraform
backendAzureRmStorageAccountName: courtesycardsterraform
thekaveman marked this conversation as resolved.
Show resolved Hide resolved
backendAzureRmContainerName: tfstate
backendAzureRmKey: terraform.tfstate
- task: TerraformTaskV3@3
displayName: Select environment
inputs:
provider: azurerm
command: custom
customCommand: workspace
commandOptions: select $(workspace)
workingDirectory: "$(System.DefaultWorkingDirectory)/terraform"
# service connection
environmentServiceNameAzureRM: deployer
- task: TerraformTaskV3@3
displayName: Terraform apply
inputs:
provider: azurerm
command: apply
# (ditto the lock comment above)
commandOptions: -input=false -lock-timeout=5m $(Pipeline.Workspace)/savedPlan/tfplan
workingDirectory: "$(System.DefaultWorkingDirectory)/terraform"
# service connection
environmentServiceNameAzureRM: deployer
19 changes: 19 additions & 0 deletions terraform/pipeline/tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import os
import re

REASON = os.environ["REASON"]
# use variable corresponding to tag triggers
SOURCE = os.environ["INDIVIDUAL_SOURCE"]
IS_TAG = os.environ["IS_TAG"].lower() == "true"
thekaveman marked this conversation as resolved.
Show resolved Hide resolved

if REASON == "IndividualCI" and IS_TAG:
if re.fullmatch(r"20\d\d.\d\d.\d+-rc\d+", SOURCE):
tag_type = "test"
elif re.fullmatch(r"20\d\d.\d\d.\d+", SOURCE):
tag_type = "prod"
else:
tag_type = None
else:
tag_type = None

print(tag_type)
6 changes: 6 additions & 0 deletions terraform/pipeline/workspace.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import os
import re
import sys

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

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