The last couple of posts have been dealing with Release managed from the Releases area under Azure Pipelines. This week we are going to take what we were doing in that separate area of Azure DevOps and instead make it part of the YAML that currently builds our application. If you need some background on how the project got to this point check out the following posts.
Getting Started with Azure DevOps
Pipeline Creation in Azure DevOps
Azure DevOps Publish Artifacts for ASP.NET Core
Azure DevOps Pipelines: Multiple Jobs in YAML
Azure DevOps Pipelines: Reusable YAML
Azure DevOps Pipelines: Use YAML Across Repos
Azure DevOps Pipelines: Conditionals in YAML
Azure DevOps Pipelines: Naming and Tagging
Azure DevOps Pipelines: Manual Tagging
Azure DevOps Pipelines: Depends On with Conditionals in YAML
Azure DevOps Pipelines: PowerShell Task
Azure DevOps Releases: Auto Create New Release After Pipeline Build
Azure DevOps Releases: Auto Create Release with Pull Requests
Recap
The current setup we have uses a YAML based Azure Pipeline to build a couple of ASP.NET Core web applications. Then on the Release side, we have basically a dummy release that doesn’t actually do anything but served as a demo of how to configure a continuous deployment type release. The following is the current YAML for our Pipeline for reference.
name: $(SourceBranchName)_$(date:yyyyMMdd)$(rev:.r) resources: repositories: - repository: Shared name: Playground/Shared type: git ref: master #branch name trigger: none variables: buildConfiguration: 'Release' jobs: - job: WebApp1 displayName: 'Build WebApp1' pool: vmImage: 'ubuntu-latest' steps: - task: PowerShell@2 inputs: targetType: 'inline' script: 'Get-ChildItem -Path Env:\' - template: buildCoreWebProject.yml@Shared parameters: buildConFiguration: $(buildConfiguration) project: WebApp1.csproj artifactName: WebApp1 - job: WebApp2 displayName: 'Build WebApp2' condition: and(succeeded(), eq(variables['BuildWebApp2'], 'true')) pool: vmImage: 'ubuntu-latest' steps: - template: build.yml parameters: buildConFiguration: $(buildConfiguration) project: WebApp2.csproj artifactName: WebApp2 - job: DependentJob displayName: 'Build Dependent Job' pool: vmImage: 'ubuntu-latest' dependsOn: - WebApp1 - WebApp2 steps: - template: buildCoreWebProject.yml@Shared parameters: buildConFiguration: $(buildConfiguration) project: WebApp1.csproj artifactName: WebApp1Again - job: TagSources displayName: 'Tag Sources' pool: vmImage: 'ubuntu-latest' dependsOn: - WebApp1 - WebApp2 - DependentJob condition: | and ( eq(dependencies.WebApp1.result, 'Succeeded'), in(dependencies.WebApp2.result, 'Succeeded', 'Skipped'), in(dependencies.DependentJob.result, 'Succeeded', 'Skipped') ) steps: - checkout: self persistCredentials: true clean: true fetchDepth: 1 - task: PowerShell@2 inputs: targetType: 'inline' script: | $env:GIT_REDIRECT_STDERR` = '2>&1' $tag = "manual_$(Build.BuildNumber)".replace(' ', '_') git tag $tag Write-Host "Successfully created tag $tag" git push --tags Write-Host "Successfully pushed tag $tag" failOnStderr: false
The above setup works great, but in April of this year, Azure Pipelines got the concept of multi-stage Pipelines which gives us the ability to manage the Release side of things in the same YAML as our builds and allows releases to be source controlled and different per branch in the same way that builds in YAML can be.
Simplified Build YAML
The above is the full YAML for our sample builds, which is a lot of code. The following is a paired down version that we will be using for the rest of this post that only builds WebApp1 and should help the changes stand out.
name: $(SourceBranchName)_$(date:yyyyMMdd)$(rev:.r) resources: repositories: - repository: Shared name: Playground/Shared type: git ref: master #branch name trigger: none variables: buildConfiguration: 'Release' jobs: - job: WebApp1 displayName: 'Build WebApp1' pool: vmImage: 'ubuntu-latest' steps: - task: PowerShell@2 inputs: targetType: 'inline' script: 'Get-ChildItem -Path Env:\' - template: buildCoreWebProject.yml@Shared parameters: buildConFiguration: $(buildConfiguration) project: WebApp1.csproj artifactName: WebApp1
Adding Stages
Stages are an extra layer of grouping that help divide a Pipeline similar to how jobs work except at a higher level. Jobs are a group of Steps, but Stages are a group of Jobs. In the following YAML, you can see that our existing jobs have been grouped under a Build stage and a new Release stage has been added.
name: $(SourceBranchName)_$(date:yyyyMMdd)$(rev:.r) resources: repositories: - repository: Shared name: Playground/Shared type: git ref: master #branch name trigger: none variables: buildConfiguration: 'Release' stages: - stage: Build jobs: - job: WebApp1 displayName: 'Build WebApp1' pool: vmImage: 'ubuntu-latest' steps: - task: PowerShell@2 inputs: targetType: 'inline' script: 'Get-ChildItem -Path Env:\' - template: buildCoreWebProject.yml@Shared parameters: buildConFiguration: $(buildConfiguration) project: WebApp1.csproj artifactName: WebApp1 - stage: Deploy jobs: - job: Deploy steps: - script: echo Fake deploying code
When adding stages watch your whitespace it is easy to miss spacing in your existing code when wrapping them in stages.
Results
After running the Pipeline with the above changes you will see on the Pipeline’s summary page that it will display the results of each stage.
In the detailed view of a specific Pipeline run, there will now be a Stages tab that shows the results by stage. If you hit the expander on a stage it will also give you an option to rerun a stage if you ever have that need.
Wrapping Up
Hopefully, this will help you get a jump start on setting up your own multi-stage Pipelines. While I’m still not in love with YAML it is nice to have builds and releases in source control with the ability to vary by branch when you have the need. This setup works great if you want all your stages to run every time. A follow-up post will look at how to make a stage that requires approval.
Also published on Medium.
Pingback: Top Stories from the Microsoft DevOps Community – 2020.07.24 - Microsoft Today