Blend Master Software Get In Touch

Get In Touch

Prefer using email? Say hi at [email protected]

Backup your Azure DevOps variable groups to a Git repo

by

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:

  1. Create a new Git repo in your Azure DevOps project. This repository will be used as the backup destination.
  2. 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.


Vassili Altynikov
About the author

Principal DevOps Architect at Blend Master Software