Azure Pipelines

Azure Pipelines – Shared Templates and Versioning

Sharing templates seems like a really simple thing to want to do especially when you have similar projects that use the same steps to build or deploy code. In this post I am going to go through how does Azure Pipelines support this and how can we version shared templates.

As a C# developer I build code the same way in multiple projects, I use GitVersion for versioning my code and the DotNetCoreCLI tasks to restore, build and publish my code into and artifact, for example

steps:
- task: gitversion/setup@0
  displayName: 'Install GitVersion'
  inputs:
    versionSpec: '5.x'
- task: gitversion/execute@0
  displayName: 'Determine Version'
  inputs:
    additionalArguments: '/updateprojectfiles'
- task: DotNetCoreCLI@2
  displayName: 'Restore Packages'
  inputs:
    command: 'restore'
    projects: '**/*.csproj'
- task: DotNetCoreCLI@2
  displayName: 'Build Code'
  inputs:
    command: 'build'
    projects: '**/*.csproj'
    arguments: '--no-restore --configuration release'
- task: DotNetCoreCLI@2
  displayName: 'Publish Code'
  inputs:
    command: 'publish'
    arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)/release'
    projects:  'src/**/*.csproj'
    publishWebProjects: false
    modifyOutputPath: false
    zipAfterPublish: true    
- publish: '$(Build.ArtifactStagingDirectory)'
  displayName: 'Publish App Artifact'
  artifact: 'myapp'

There is probably a dozen projects/repositories where this exists now and say I wanted to add a security scan to all my projects that may take some time. A shared template would allow this to be maintained and updated in one place.

So how can we create a shared template that is used across multiple repositories in Azure DevOps?

Firstly we need to create a repository that contains the shared template code, I called my repository ‘shared-templates’, I added a folder called ‘code’ and added the above code into a template called ‘build-code.yml’ and added a parameter to pass in the name of the artifact

parameters:
- name: artifactName
  type: string

Now I have added the first version of the code I am going to tag the repo with a version

The shared templates repository is now ready for use, let’s update a pipeline to now use this. The current pipeline looks something like this:

trigger: none
pool:
  vmImage: 'ubuntu-latest'

steps:
- task: gitversion/setup@0
  displayName: Install GitVersion
  inputs:
    versionSpec: '5.x'
- task: gitversion/execute@0
  displayName: Determine Version
  inputs:
    additionalArguments: '/updateprojectfiles'
- task: DotNetCoreCLI@2
  displayName: Restore Packages
  inputs:
    command: 'restore'
    projects: '**/*.csproj'
- task: DotNetCoreCLI@2
  displayName: Build Code
  inputs:
    command: build
    projects: '**/*.csproj'
    arguments: '--no-restore --configuration release'
- task: DotNetCoreCLI@2
  displayName: Publish Code
  inputs:
    command: publish
    arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)/release'
    projects:  'src/**/*.csproj'
    publishWebProjects: false
    modifyOutputPath: false
    zipAfterPublish: true    
- publish: '$(Build.ArtifactStagingDirectory)'
  displayName: 'Publish App Artifact'
  artifact: 'myapp'

In order to use the external repository we need to add a resources section to the pipeline to look for the shared-templates repository and which branch to look at

resources:
  repositories:
    - repository: templates
      type: git
      name: shared-templates
      ref: main

The next thing is to replace all the previous steps with a template step and then tag the repository with the version from GitVersion

trigger: none
pool:
  vmImage: 'ubuntu-latest'

resources:
  repositories:
    - repository: templates
      type: git
      name: shared-templates
      ref: main

steps:
- template: 'code/build-code.yml@templates'
  parameters:
    artifactName: 'myapp'
- bash: |
      git tag $(SemVer)
      echo 'Created tag'
      git push --tags
     echo 'tags pushed'
  displayName: 'Creating Tag'  

This will get the latest version of the shared-templates when it is ran, if we wanted to get a specific version we can reference a tag like one we created earlier. The above would then become

trigger: none
pool:
  vmImage: 'ubuntu-latest'

resources:
  repositories:
    - repository: templates
      type: git
      name: shared-templates
      ref: ref/tags/v1.0

steps:
- template: 'code/build-code.yml@templates'
  parameters:
    artifactName: 'myapp'
- bash: |
      git tag $(SemVer)
      echo 'Created tag'
      git push --tags
     echo 'tags pushed'
  displayName: 'Creating Tag'  

And that’s it, a way of sharing templates that can be used in multiple projects and can also be versioned to mitigate breaking changes in shared code.

I hope this has been useful, it’s certainly a technique I will be using more of in my Azure Pipelines.