We rely on variables groups1 in Azure DevOps heavily to manage shared global and environment-specific configuration values used across multiple pipelines. Variable groups are a great way to centralize pipeline configuration but they currently have a few limitations.
First, there’s no audit history so it’s very difficult to figure out who changed a given variable and when.
Second, there’s no backup or export functionality available for variable groups so if someone accidentally deletes a variable group, it can be very time consuming to restore it.
We built a simple solution to backup all Azure DevOps variable groups within a project to a Git repo on a scheduled basis using Azure DevOps CLI in a YAML pipeline2. The solution is available on GitHub and you are welcome to use and customize it to your own needs. If you’d like to learn more about how it works, please keep reading.
Initial setup
Before we jump into the implementation details, there’s a couple of prerequisites to take care of:
- Create a new Git repo in your Azure DevOps project. This repository will be used as the backup destination.
- Grant the Project Collection Build Service Contribute permission to the Git repo3. That’s the service account the pipeline will execute under and it needs to be able to commit changes to the repo.
Triggers
Since we’ll be committing changes to the repo on a scheduled basis, the CI trigger should be disabled:
trigger: none
Schedules
The schedule is going to be configured to run every 15 minutes Monday through Friday 9 AM to 5 PM (UTC-05:00), even when there are no changes in the repo4.
This is where you can adjust the schedule and frequency of the variable group snapshots by changing the cron expression5.
schedules:
- cron: "*/15 14-21 * * Mon-Fri"
displayName: Every 15 min M-F 9am-4:45pm (UTC-05:00)
branches:
include:
- master
always: true
- cron: "0 22 * * Mon-Fri"
displayName: M-F 5pm (UTC-05:00)
branches:
include:
- master
always: true
Agent pool
We’ll use the ubuntu-latest
Microsoft-hosted agent pool:
pool:
vmImage: 'ubuntu-latest'
Steps
Finally, the steps to run:
steps:
Checkout
Checkout the Git repo and allow scripts to access the system token6.
- checkout: self
persistCredentials: true
clean: true
Install prerequisites
Update python
, pip
and Azure CLI. Install Azure DevOps extention.
# Updating the python version available on the linux agent
- task: UsePythonVersion@0
inputs:
versionSpec: '3.x'
architecture: 'x64'
# Updating pip to latest
- script: python -m pip install --upgrade pip
displayName: 'Upgrade pip'
# Updating to latest Azure CLI version.
- script: pip install --pre azure-cli --extra-index-url https://azurecliprod.blob.core.windows.net/edge
displayName: 'Upgrade Azure CLI'
- script: az --version
displayName: 'Show Azure CLI version'
- script: az extension add -n azure-devops
displayName: 'Install Azure DevOps Extension'
Login Azure DevOps Extension
Login Azure DevOps Extension using the system token:
- script: echo ${AZURE_DEVOPS_CLI_PAT} | az devops login
env:
AZURE_DEVOPS_CLI_PAT: $(System.AccessToken)
displayName: 'Login Azure DevOps Extension'
Set default Azure DevOps organization and project
Set the organization
and project
parameter defaults to use the current Azure DevOps organization and project. This way we won’t need to supply these arguments to each azure devops
CLI command in the following step.
- script: az devops configure --defaults organization=$(System.TeamFoundationCollectionUri) project="$(System.TeamProject)"
displayName: 'Set default Azure DevOps organization and project'
Save variable groups
This is the main script to put it all together!
We are going to iterate over the existing variable groups, save them as separate JSON files (that includes metadata and all variable values) and commit changes by impersonating the user who last modified each variable group.
Any variable groups that haven’t changed since the last run are going to be ignored by Git since the generated JSON files are going to be identical so the Git commit history will clearly indicate what’s changed, when and by whom.
- pwsh: |
# Checkout the source branch
git checkout $(Build.SourceBranchName)
# Get all variable groups
$groups = ConvertFrom-Json "$(az pipelines variable-group list)"
$groups | foreach {
$groupName = $_.name
# Prepend VariableGroups folder name
$filePath = Join-Path "VariableGroups" "$groupName.json"
# Save the variable group to a file
ConvertTo-Json $_ | New-Item $filePath -Force
# Use the last modified user's name and email
git config user.email $_.modifiedBy.uniqueName
git config user.name $_.modifiedBy.displayName
# Stage the file
git add $filePath
# Commit
git commit -m "Variable group $groupName updates"
}
# Push all changes
git push origin
displayName: 'Save variable groups'
YAML pipeline
Here’s the full contents of the azure-pipelines.yaml file:
trigger: none
schedules:
- cron: "*/15 14-21 * * Mon-Fri"
displayName: Every 15 min M-F 9am-4:45pm (UTC-05:00)
branches:
include:
- master
always: true
- cron: "0 22 * * Mon-Fri"
displayName: M-F 5pm (UTC-05:00)
branches:
include:
- master
always: true
pool:
vmImage: 'ubuntu-latest'
steps:
- checkout: self
persistCredentials: true
clean: true
# Updating the python version available on the linux agent
- task: UsePythonVersion@0
inputs:
versionSpec: '3.x'
architecture: 'x64'
# Updating pip to latest
- script: python -m pip install --upgrade pip
displayName: 'Upgrade pip'
# Updating to latest Azure CLI version.
- script: pip install --pre azure-cli --extra-index-url https://azurecliprod.blob.core.windows.net/edge
displayName: 'Upgrade Azure CLI'
- script: az --version
displayName: 'Show Azure CLI version'
- script: az extension add -n azure-devops
displayName: 'Install Azure DevOps Extension'
- script: echo ${AZURE_DEVOPS_CLI_PAT} | az devops login
env:
AZURE_DEVOPS_CLI_PAT: $(System.AccessToken)
displayName: 'Login Azure DevOps Extension'
- script: az devops configure --defaults organization=$(System.TeamFoundationCollectionUri) project="$(System.TeamProject)"
displayName: 'Set default Azure DevOps organization and project'
- pwsh: |
# Checkout the source branch
git checkout $(Build.SourceBranchName)
# Get all variable groups
$groups = ConvertFrom-Json "$(az pipelines variable-group list)"
$groups | foreach {
$groupName = $_.name
# Prepend VariableGroups folder name
$filePath = Join-Path "VariableGroups" "$groupName.json"
# Save the variable group to a file
ConvertTo-Json $_ | New-Item $filePath -Force
# Use the last modified user's name and email
git config user.email $_.modifiedBy.uniqueName
git config user.name $_.modifiedBy.displayName
# Stage the file
git add $filePath
# Commit
git commit -m "Variable group $groupName updates"
}
# Push all changes
git push origin
displayName: 'Save variable groups'
Was this helpful? We’d love to hear your story! Please leave a comment.
-
https://docs.microsoft.com/en-us/azure/devops/pipelines/library/variable-groups?view=azure-devops&tabs=yaml ↩
-
https://docs.microsoft.com/en-us/azure/devops/cli/azure-devops-cli-in-yaml?view=azure-devops ↩
-
https://docs.microsoft.com/en-us/azure/devops/pipelines/scripts/git-commands?view=azure-devops&tabs=yaml#grant-version-control-permissions-to-the-build-service ↩
-
https://docs.microsoft.com/en-us/azure/devops/pipelines/process/scheduled-triggers?view=azure-devops&tabs=yaml#running-even-when-there-are-no-code-changes ↩
-
https://docs.microsoft.com/en-us/azure/devops/pipelines/process/scheduled-triggers?view=azure-devops&tabs=yaml#supported-cron-syntax ↩
-
https://docs.microsoft.com/en-us/azure/devops/pipelines/scripts/git-commands?view=azure-devops&tabs=yaml#allow-scripts-to-access-the-system-token ↩