Azure Pipelines

Azure DevOps Multi-Stage Pipelines: Require Stage Approval

In last week’s post, we covered taking our existing build pipeline and making it a multi-stage Pipeline with a build stage and a deploy stage. This week we are going to add another stage to our pipeline for production. Since we don’t want the production stage deployed before it has been through QA we will need to hold the stage until it is verified ready, which is what this post is going to be about. If you haven’t read last week’s post, Azure DevOps Pipelines: Multi-Stage Pipelines, you might want to start there before reading the rest of this post if you are new to multi-stage pipelines.

 

Add an Environment

In order to require approval on a stage is to associate it with and environment and add the approval requirement to the environment. In Azure DevOps under Pipelines select Environments and then click the Create environment button.

On the New environment dialog fill in a Name. If you had actual resources associated with the environment they can be added to provide traceability, but in this example, we are going to stick with the None option.

Require Approval for an Environment

Now that the resource has been created on its details page we can use the three dots to open the menu and click Approvals and checks.

On the next screen click the +button in the upper right corner and then from the lists of check select Approvals and then click Next. As you can see from the partial list in the screenshot the range of check available for approvals is massive.

The next dialog is used to select the uses or groups who should be able to perform approves for the environment. When your approves are set click Create to finish.

Use an Environment in a Pipeline

If you are following along from a previous post not that the Deploy stage has been renamed to QA to make the Pipeline results clearer.

Before:
- stage: Deploy
  jobs:
  - job: Deploy
    steps:
      - script: echo Fake deploying code

After:
- stage: QA
  jobs:
  - job: DeployQa
    steps:
      - script: echo Deploying to QA

Now we are going to add a new stage for our production environment. Notice that instead of a normal job we are using a deployment job which enables us to specify our desired environment. Deployment jobs have a ton more features than we are using so make sure and check out the docs to see what other options are available.

- stage: Production
  jobs:
  - deployment: DeployProduction
    environment: 'Production'
    strategy:
     runOnce:
       deploy:
         steps:
          - script: echo Deploying to Production

Save and run the Pipeline and we will look at how this new state presents differently than the Build and QA stages.

Pipeline Results

As you can see in the following screenshot the results of the Pipeline run have has a section notifying that the Production stage can’t run until it has been reviewed. Also, notice in the Stages section that the Production stage shows a Waiting status.

Clicking the Review button will show a dialog that will allow you to approve or reject the stage that is waiting.

Wrapping Up

The extra layer of options provided environments enables most of the scenarios that I missed when I first started playing with multi-stage Pipelines. Having the build and release steps for an app in source control and the added ability to vary them by branch makes this worth it even if I do have to deal with more YAML.

Azure DevOps Pipelines: Multi-Stage Pipelines

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: [email protected]
    inputs:
      targetType: 'inline'
      script: 'Get-ChildItem -Path Env:\'

  - template: [email protected]
    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: [email protected]
    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: P[email protected]
    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: [email protected]
    inputs:
      targetType: 'inline'
      script: 'Get-ChildItem -Path Env:\'

  - template: [email protected]
    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: [email protected]
      inputs:
        targetType: 'inline'
        script: 'Get-ChildItem -Path Env:\'

    - template: [email protected]
      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.

Azure DevOps Releases: Auto Create Release with Pull Requests

Last week we covered auto-creating Release when a build completes. This week we are going to cover how to create a release when a build from a pull request completes. This setup would be helpful for verification of changes before the actual make it into a releasable branch. The following posts will help you catch up if you’re new to the series.

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

Build Validation Branch Policy

Before we can have a Release created with a pull request we have to make sure that the pull request process does a build. I’m going to review how to do this quickly, for more info see my Branch Policies post. To do this we are going to head over to the Repos section of Azure DevOps. In the Branches section on the branch we want a build on a pull request for select the three dots and then click Branch policies.

On the repo settings page scroll down to the Build Validation section and click the + button to add a build to the pull request process.

The Add build policy dialog has a few options, but we are taking the defaults. Do note that if you have multiple Build pipelines that you make sure and adjust that option to the correct build. Click Save when you’re done.

Release Changes to allow Pull Request Trigger

Based on how we set up our Release to trigger when a build complete you might think that using the same build to validate a Pull Request would automatically trigger a new Release, but that isn’t the case. In most cases, this is actually a good thing since you wouldn’t want to deploy change before they have been reviewed. In the case we are trying to cover our release is to a QA environment so the requested changes can be verified before they make it into a releasable branch.

To enable a Release to be created from a pull request we need to head over to the Pipeline > Release area in Azure DevOps. Once there with the release in question selected click the Edit button.

In the Artifacts, section click the lightning bolt to edit the continuous deployment triggers.

Near the middle of the dialog, we want to Enable the Pull request trigger. Doing this will also require you to enter Target Branch Filters which are the branches that will be allowed to trigger a release when they are a target of a pull request.

Next, we need to enable our sample stage to be deployed for releases based on pull requests. Click the lightning bolt on the left side of the stage to edit the pre-deployment conditions.

On the dialog that shows Enable the Pull request deployment setting.

After closing the dialog make sure and Save the release.

Results

To show the results I created a new branch with a small change and created a PR into the master branch. From the Pull Request, we can click the View all checks button to see the status of the required build.

On the Checks dialog, you can see at the bottom that our sample release ran successfully.

If you click on the release you can see in the artifacts section that the files being used are from the pull request’s merge branch, not the branch being PRed or the target branch.

Wrapping Up

Building and releasing on a pull request opens up a lot of options, especially around making sure your code is verified before making it into a release branch.

Azure DevOps Releases: Auto Create New Release After Pipeline Build

I hit a snag with my planned post for this weeks and decided to hope back over to Azure DevOps and show how to create a Release and have that Release be triggered anytime a build Pipeline complete successfully. This post is going to be using the same Azure DevOps Project as all the previous posts in this series which are linked below.

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

Creating a Release

In Azure DevOps to create a release go to Pipelines and then Releases. Since this is our project’s first Release we have a New pipeline button to click to start the creation process.

The New pipeline button will start the creation process by showing a Select a template dialog. Since our release isn’t really going to do anything yet we are going to click the Empty job option.

Next, we want to add the artifacts from our build Pipeline to this Release. Click Add an artifact to start the process.

The Add an artifact dialog will show which allows us a lot of options on the source of the artifact. For this setup, we are going to use a Source type of Build since our artifacts are the result of an Azure DevOps Build. The next option we need to select is Source which is where we select which Azure DevOps Build Pipeline we want to use artifacts from. In this sample case, we only have a single option. Once the Source is selected a couple more options will show up, but we are taking the default values for those and just clicking the Add button.

Now that we have the basic release setup click the Save button. There will be a prompt for a folder. Either enter a folder name or leave blank to keep the release in the root and then click OK to complete the save.

Auto Create After Pipeline Build

Now that we have our basic Release Pipeline we are going to set up a continuous deployment trigger on the artifact so that any time a new build is completed. In the Artifacts area click the Lighting Bolt on the artifact the trigger should be on, we only have one option in our sample.

A dialog will show different trigger options. We are going to Enable the Continuous deployment trigger. Notice that when the trigger is enabled there is an option to only trigger for specific branches. I have used this option pretty often to only trigger a release when a build is done on master. When done click the X to close the dialog and then Save the release. I also renamed the release to Web App 1 & 2 to make it clearer what the release is doing.

Now pop over to the Pipelines and run a build. After the build is complete go back to the Release and you will see that a new release was triggered, and deployed in this case based on how the rest of the Release was set up.

Wrapping Up

In this post, we created our first Release, which doesn’t actually do anything, and configured it to deploy automatically on the build of our application. I use a similar setup to automatically deploy to a QA environment. This setup can open up a ton of scenarios.

Azure DevOps Pipelines: PowerShell Task

This is going to be a quick post that shows the use of the PowerShell task in a Pipeline. Nothing in the post is really specific to the Azure DevOps Project we have been using over the last few weeks, but just in case you’re totally new to Azure DevOps and/or this series you can use the following posts to get started.

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

PowerShell Task

The PowerShell task will allow you to do pretty much anything. If there isn’t an existing DevOps task that fits your needs more than likely you can find a way to use the PowerShell task to accomplish what you need within the context of the computer the task is running on and even external computers that depending on your networking and security setup. The following is a sample task I added to a Pipeline that will output all the environment variables to the logs. This is an inline script, but you can also run scripts from files. Also, note that this works on both Windows and Linux agents.

- task: [email protected]
  inputs:
    targetType: 'inline'
    script: 'Get-ChildItem -Path Env:\'

While this script isn’t super useful for a production Pipeline I often use it when setting up a Pipeline to get a good feel for what is available variable wise. Also, keep in mind that depending on the trigger of the run these variables can be different. For example, if a run was triggered by a pull request you will have a number of pull request related variables. The following is the output of this command on my test project that was triggered via a pull request and therefore contains a bunch of SYSTEM_PULLREQUEST_x variables with information about the pull request. The agent was running Linux.

Name                           Value
----                           -----
AGENT_ACCEPTTEEEULA            True
AGENT_BUILDDIRECTORY           /home/vsts/work/1
AGENT_DISABLELOGPLUGIN_TESTFI… true
AGENT_DISABLELOGPLUGIN_TESTRE… true
AGENT_HOMEDIRECTORY            /home/vsts/agents/2.165.2
AGENT_ID                       9
AGENT_JOBNAME                  Build WebApp1
AGENT_JOBSTATUS                Succeeded
AGENT_MACHINENAME              fv-az563
AGENT_NAME                     Hosted Agent
AGENT_OS                       Linux
AGENT_OSARCHITECTURE           X64
AGENT_READONLYVARIABLES        true
AGENT_RETAINDEFAULTENCODING    false
AGENT_ROOTDIRECTORY            /home/vsts/work
AGENT_TEMPDIRECTORY            /home/vsts/work/_temp
AGENT_TOOLSDIRECTORY           /opt/hostedtoolcache
AGENT_VERSION                  2.165.2
AGENT_WORKFOLDER               /home/vsts/work
agent.jobstatus                Succeeded
ANDROID_HOME                   /usr/local/lib/android/sdk
ANDROID_SDK_ROOT               /usr/local/lib/android/sdk
ANT_HOME                       /usr/share/ant
AZURE_EXTENSION_DIR            /opt/az/azcliextensions
AZURE_HTTP_USER_AGENT          VSTS_08ccc6b2-4e5e-4621-8f5b-3fe0de2efa22_build…
BOOST_ROOT_1_69_0              /usr/local/share/boost/1.69.0
BOOST_ROOT_1_72_0              /usr/local/share/boost/1.72.0
BUILD_ARTIFACTSTAGINGDIRECTORY /home/vsts/work/1/a
BUILD_BINARIESDIRECTORY        /home/vsts/work/1/b
BUILD_BUILDID                  73
BUILD_BUILDNUMBER              merge_20200422.1
BUILD_BUILDURI                 vstfs:///Build/Build/73
BUILD_CONTAINERID              3453972
BUILD_DEFINITIONNAME           Playground
BUILD_DEFINITIONVERSION        4
BUILD_QUEUEDBY                 Microsoft.VisualStudio.Services.TFS
BUILD_QUEUEDBYID               00000002-0000-8888-8000-000000000000
BUILD_REASON                   PullRequest
BUILD_REPOSITORY_CLEAN         False
BUILD_REPOSITORY_GIT_SUBMODUL… False
BUILD_REPOSITORY_ID            ff7a6325-1129-42e3-b095-6a39ef6a6bd3
BUILD_REPOSITORY_LOCALPATH     /home/vsts/work/1/s
BUILD_REPOSITORY_NAME          Playground
BUILD_REPOSITORY_PROVIDER      TfsGit
BUILD_REPOSITORY_URI           https://[email protected]/ericlanders…
BUILD_REQUESTEDFOR             Eric Anderson
BUILD_REQUESTEDFOREMAIL        [email protected]
BUILD_REQUESTEDFORID           45247cb1-8f49-4c03-a4c5-b03ac3286c99
BUILD_SOURCEBRANCH             refs/pull/10/merge
BUILD_SOURCEBRANCHNAME         merge
BUILD_SOURCESDIRECTORY         /home/vsts/work/1/s
BUILD_SOURCEVERSION            3e2b77c27f31a4c729a5f195b49d2e108500399d
BUILD_SOURCEVERSIONAUTHOR      Eric Anderson
BUILD_SOURCEVERSIONMESSAGE     Merge pull request 10 from docChanges into mast…
BUILD_STAGINGDIRECTORY         /home/vsts/work/1/a
BUILDCONFIGURATION             Release
BUILDWEBAPP2                   false
CHROME_BIN                     /usr/bin/google-chrome
CHROMEWEBDRIVER                /usr/local/share/chrome_driver
COMMON_TESTRESULTSDIRECTORY    /home/vsts/work/1/TestResults
CONDA                          /usr/share/miniconda
DEBIAN_FRONTEND                noninteractive
DOTNET_SKIP_FIRST_TIME_EXPERI… 1
ENDPOINT_URL_SYSTEMVSSCONNECT… https://dev.azure.com/ericlanderson/
GECKOWEBDRIVER                 /usr/local/share/gecko_driver
GIT_TERMINAL_PROMPT            0
GOROOT                         /usr/local/go1.14
GOROOT_1_11_X64                /usr/local/go1.11
GOROOT_1_12_X64                /usr/local/go1.12
GOROOT_1_13_X64                /usr/local/go1.13
GOROOT_1_14_X64                /usr/local/go1.14
GRADLE_HOME                    /usr/share/gradle
HOME                           /home/vsts
ImageOS                        ubuntu18
ImageVersion                   20200406.2
INPUT_ARGUMENTS                
INVOCATION_ID                  3e6abb812a484ab39fadc9e8721258ee
JAVA_HOME                      /usr/lib/jvm/zulu-8-azure-amd64
JAVA_HOME_11_X64               /usr/lib/jvm/zulu-11-azure-amd64
JAVA_HOME_12_X64               /usr/lib/jvm/zulu-12-azure-amd64
JAVA_HOME_7_X64                /usr/lib/jvm/zulu-7-azure-amd64
JAVA_HOME_8_X64                /usr/lib/jvm/zulu-8-azure-amd64
JOURNAL_STREAM                 9:30085
LANG                           C.UTF-8
LEIN_HOME                      /usr/local/lib/lein
LEIN_JAR                       /usr/local/lib/lein/self-installs/leiningen-2.9…
M2_HOME                        /usr/share/apache-maven-3.6.3
MSDEPLOY_HTTP_USER_AGENT       VSTS_08ccc6b2-4e5e-4621-8f5b-3fe0de2efa22_build…
PATH                           /opt/microsoft/powershell/7:/usr/share/rust/.ca…
PIPELINE_WORKSPACE             /home/vsts/work/1
POWERSHELL_DISTRIBUTION_CHANN… Azure-DevOps-ubuntu18
PSModulePath                   /home/vsts/.local/share/powershell/Modules:/usr…
RUNNER_TOOLSDIRECTORY          /opt/hostedtoolcache
SELENIUM_JAR_PATH              /usr/share/java/selenium-server-standalone.jar
SWIFT_PATH                     /usr/share/swift/usr/bin
SYSTEM                         build
SYSTEM_ARTIFACTSDIRECTORY      /home/vsts/work/1/a
SYSTEM_COLLECTIONID            08ccc6b2-4e5e-4621-8f5b-3fe0de2efa22
SYSTEM_COLLECTIONURI           https://dev.azure.com/ericlanderson/
SYSTEM_CULTURE                 en-US
SYSTEM_DEFAULTWORKINGDIRECTORY /home/vsts/work/1/s
SYSTEM_DEFINITIONID            5
SYSTEM_DEFINITIONNAME          Playground
SYSTEM_ENABLEACCESSTOKEN       SecretVariable
SYSTEM_HOSTTYPE                build
SYSTEM_ISSCHEDULED             False
SYSTEM_JOBATTEMPT              1
SYSTEM_JOBDISPLAYNAME          Build WebApp1
SYSTEM_JOBID                   98395c9e-7365-5c3f-03de-ec42b09a8a98
SYSTEM_JOBIDENTIFIER           WebApp1.__default
SYSTEM_JOBNAME                 __default
SYSTEM_JOBPARALLELISMTAG       Private
SYSTEM_JOBPOSITIONINPHASE      1
SYSTEM_PHASEATTEMPT            1
SYSTEM_PHASEDISPLAYNAME        Build WebApp1
SYSTEM_PHASEID                 a142d6c6-ff80-5cff-8292-5044e2c5b0ef
SYSTEM_PHASENAME               WebApp1
SYSTEM_PIPELINESTARTTIME       2020-04-22 06:11:44-05:00
SYSTEM_PLANID                  726fda14-a3a2-45b1-b745-bef8cf17bdaa
SYSTEM_PULLREQUEST_ISFORK      False
SYSTEM_PULLREQUEST_PULLREQUES… 10
SYSTEM_PULLREQUEST_PULLREQUES… 1
SYSTEM_PULLREQUEST_SOURCEBRAN… refs/heads/docChanges
SYSTEM_PULLREQUEST_SOURCECOMM… ba11cb768bc75ae65ff6b7ac6afb8a2950063f07
SYSTEM_PULLREQUEST_SOURCEREPO… https://[email protected]/ericlanders…
SYSTEM_PULLREQUEST_TARGETBRAN… refs/heads/master
SYSTEM_SERVERTYPE              Hosted
SYSTEM_STAGEATTEMPT            1
SYSTEM_STAGEDISPLAYNAME        __default
SYSTEM_STAGEID                 96ac2280-8cb4-5df5-99de-dd2da759617d
SYSTEM_STAGENAME               __default
SYSTEM_TASKDEFINITIONSURI      https://dev.azure.com/ericlanderson/
SYSTEM_TASKDISPLAYNAME         PowerShell
SYSTEM_TASKINSTANCEID          6417fa85-e8cf-55f9-817e-d698bd79d6f7
SYSTEM_TASKINSTANCENAME        PowerShell
SYSTEM_TEAMFOUNDATIONCOLLECTI… https://dev.azure.com/ericlanderson/
SYSTEM_TEAMFOUNDATIONSERVERURI https://dev.azure.com/ericlanderson/
SYSTEM_TEAMPROJECT             Playground
SYSTEM_TEAMPROJECTID           7550ca2f-9ffe-45b7-abd5-c4e92a4a5f4e
SYSTEM_TIMELINEID              726fda14-a3a2-45b1-b745-bef8cf17bdaa
SYSTEM_TOTALJOBSINPHASE        1
SYSTEM_WORKFOLDER              /home/vsts/work
TASK_DISPLAYNAME               PowerShell
TF_BUILD                       True
USER                           vsts
VCPKG_INSTALLATION_ROOT        /usr/local/share/vcpkg
VSTS_AGENT_PERFLOG             /home/vsts/perflog
VSTS_PROCESS_LOOKUP_ID         vsts_54420f58-c41f-4a43-8ce8-bbbac5023620

I don’t know about you but being able to see what paths the built-in path variables actually map to helps me a lot especially when files need to be moved around.

Wrapping Up

As stated above you can do just about anything with the PowerShell task. I have used it for everything from reading a JSON file to building a VM for QA. If you hadn’t used this task before I hope this post helped you get started and opened your eyes to the huge range of things you can do with the PowerShell task.

Azure DevOps Pipelines: Depends On with Conditionals in YAML

A few weeks ago we covered Conditionals in YAML to show how to conditionally run tasks and jobs as well as how to make a job dependent on another job. This post is going to cover combing conditional and job dependencies. If you are new to this series you can use the following posts to catch up.

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

Existing Job

As a reminder, our sample pipeline has 4 jobs. The WebApp1 job always runs, the WebApp2 job is run or skipped based on a pipeline variable, the DependentJob depends on WebApp1 and WebApp2, and finally, the TagSources job is dependent on all the previous jobs. We are going to be tweaking the TagSources job in this post. The following is the YAML for the setup of the TagSources job without its tasks.

- job: TagSources
  displayName: 'Tag Sources'
  pool:
    vmImage: 'ubuntu-latest'

  dependsOn:
  - WebApp1
  - WebApp2
  - DependentJob

With this setup WebApp1, WebApp2, and DependentJob all have to report successful or the TagSources job will be skipped. The following screenshot shows a pipeline run with the variable to build WebApp2 set to false.

As you can see the TagSources job was skipped because one of its dependent jobs was skipped.

Dependencies with Conditions

Let us say for our pipeline we want the TagSources job to run as long as all jobs were successful or if WebApp1 was successful and the WebApp2 and DependentJob jobs were skipped. To do this we are going to add a condition element and manually check the results of the dependencies as you can see in the following.

- 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')
    )

And you can see in the results the TagSources job ran even with the two skipped jobs.

Wrapping Up

If there is a simpler way to accomplish what we did above I would love to hear about it. If I remember correctly I found the above in a GitHub issue, but I don’t have the link. I’m not sure how many of you will have Pipelines that will need this, but hopefully, this will save someone some research time.

Azure DevOps Pipelines: Manual Tagging

In this week’s post, we are going to cover manually tagging instead of using the tagging feature built into Azure DevOps. This post will be using a sample Azure DevOps project built over the last few weeks of posts. If you want to see how this project has gotten to this point see 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

 

 

Why?

Tags give you information about the state of your repo when an event happens, a build for our case. This can be super useful especially when you need a place to branch for a hotfix, for example. The automatic tagging covered in my Azure DevOps Pipelines: Naming and Tagging post is the way to go if it works and it has for about 95% of my projects. I have a couple of projects where the automatic tagging would not work for some reason. Since I couldn’t work out why Azure DevOps wouldn’t tag I ended up having to add a job to the Pipeline to perform the tagging.

Permissions

To perform this manual tagging the account used in our Pipeline will need to be giving contribute permission to our repo so that it will be able to push the tag. Use the gear in the lower left to open the Project Setting page.

Select the Repositories option and then click on the specific repo you would like to change the settings for, Playground is the repo we are using in the example. Note that this setting can also be changed using the top-level Git repositories option if you want to change the permission for all of your repos.

On the Security tab under Users looks for the user than has Build Service in the name and select it. When the user is selected it will show their permissions to the right of the users. Find the Contribute option and change its value to Allow.

YAML Changes

As a quick reminder, the YAML for this project currently has 3 jobs. Two web application builds (WebApp1, WebApp2) and the third job is there to show how to use job dependencies (DependentJob). I’m going to skip showing the YAML for these existing jobs, but that if you need the full existing YAML it can be found in posts linked at the top.

Since we already have multiple jobs in our Pipeline we are going to add the tagging code as a new job. This will easily allow us to only tag when all the other jobs have run successfully. The following is the full YAML for the new job.

- job: TagSources
  displayName: 'Tag Sources'
  pool:
    vmImage: 'ubuntu-latest'

  dependsOn:
  - WebApp1
  - WebApp2
  - DependentJob
 
  steps:
  - checkout: self
    persistCredentials: true
    clean: true
    fetchDepth: 1

  - task: [email protected]
    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

First off you see that this job depends on our existing three jobs to complete successfully before this new job will run.

dependsOn: 
- WebApp1 
- WebApp2 
- DependentJob

Starting in the Steps section you will see a checkout step that is normally handled automatically by the job, but in this case, we need to use the persistCredential option so the job will still be authed and allow us to push to our git repo. The self option is used to signify the current repo/branch. See the official Checkout docs for more information.

- checkout: self
  persistCredentials: true
  clean: true
  fetchDepth: 1

The final bit of the job is the PowerShell task that performs the actual tagging and push. I found most of this on stackoverflow and/or in a GitHub issue, but it has been a while so I don’t have the links handy. The StdErr stuff was to work around some git output that didn’t affect the tagging but was causing the job to be marked as failed. Other than that it is using the standard git commands to tag and push.

- task: [email protected]
  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

Wrapping Up

I honestly hope none of you have to use this. It was a huge pain to work out. It is also to work around some sort of issue with Azure DevOps built-in tagging support. To be fair I can see where in more complex Pipelines you might need a level of flexibility that the built-in tagging couldn’t provide and this would be your only option.

Azure DevOps Pipelines: Naming and Tagging

In this week’s post, we are going to cover changing the naming of Pipeline runs to provide more information as well as tagging our source when a pipeline is run. This post will be using a sample Azure DevOps project built over the last few weeks of posts. If you want to see how this project has progressed 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

Naming

By default, Pipeline runs are naming using the current date with a number for how many times the Pipeline has run for the day. For example, the fourth build on March, 17th, 2020 would start with the name 20200317.4 plus the description of the last commit. If you have the need you can change this naming scheme by using a name element in your YAML. As with the rest of the YAML related things you have all the same information available as the rest of the Pipeline to use in building whatever name might be helpful for your situation. For our example, we are going to add the branch name to the front of the run date and count by adding the following name element to the top of our YAML file.

name: $(SourceBranchName)_$(date:yyyyMMdd)$(rev:.r)

resources:      
  repositories: 
  - repository: Shared
    name: Playground/Shared
    type: git 
    ref: master #branch name

trigger: none

The above would result in master_20200317.4 using the same example as above. The following screenshot shows the actual results from this change in the sample Pipeline.

Tagging

Tagging source code when running a Pipeline is a helpful way to know exactly what was included when a Pipeline is run. Here we are going to walk through using Azure DevOps to automatically tag on successful builds. From the Pipeline, you want to tag click the Edit button as you would if you were going to edit the Pipeline’s YAML. Then click the three dots and select Triggers.

Now click on the YAML tab, then Get sources, under Tag sources we are going to select On success so tags will only happen if the build completes successfully. Also, notice the Tag format which allows you to change how the tag is named. When done make sure and Save your changes.

After running a build with the above changes head over to the Repos area of the project. From Files click on History and from there you can see the tag on the last commit that was included in the build, which is displayed here as master_20200325.1.

Wrapping Up

Using clear naming for your builds can give you a lot of information at a glance, but it does take some thought to make sure information your including is helpful. Tagging is also super helpful when viewing history to know what went out with what release, and of course, they can also be used for branching. Come back next week for a look at how to manually tag when Azure DevOps automatic tagging doesn’t work for whatever reason.

Azure DevOps Pipelines: Conditionals in YAML

In this week’s post, we are going to cover some ways to make tasks and jobs run conditionally. This will include options such as Pipeline variables to jobs that are dependent on other jobs. This post will be using a sample Azure DevOps project built over the last few weeks of posts. If you want to see the build-up 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

Sample YAML

The following YAML is based on the YAML from the previous posts, see links above, expanded with examples of using some ways of conditionally running some task or job. This is the full file for reference and the rest of the post will call out specific parts of the file as needed.

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:
  - template: [email protected]
    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: [email protected]
    parameters:
      buildConFiguration: $(buildConfiguration)
      project: WebApp1.csproj
      artifactName: WebApp1Again

Job Dependencies

The more complex pipelines get the more likely the pipeline will end up with a job that can’t run until other jobs have completed. The YAML above defines three different jobs, WebApp1, WebApp2, and DependentJob. I’m sure you have guessed by now that the third job is the one that has a dependency. To make a job dependent on other jobs we use the dependsOn element and list the jobs that must complete before the job in question can run. The following is the YAML for the sample DependentJob with the dependsOn section highlighted.

- job: DependentJob
  displayName: 'Build Dependent Job'
  pool:
    vmImage: 'ubuntu-latest'

  dependsOn:
  - WebApp1
  - WebApp2

  steps:
  - template: [email protected]
    parameters:
      buildConFiguration: $(buildConfiguration)
      project: WebApp1.csproj
      artifactName: WebApp1Again

With the above setup, DependentJob will only run if both the WebApp1 and WebApp2 jobs complete successfully.

Conditions

Conditions are a way to control if a Job or Task is run. The following example is at the job level, but the same concept works at the task level. Notice the highlighted condition.

- 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

The above condition will cause the WebApp2 job to be skipped if the BuildWebApp2 variable isn’t true. For more details on how to use conditions see the Conditions docs.

Creating a Pipeline Variable

The rest of the post is going to walk through creating a Pipeline variable and then running some sample builds to show how depends on and the conditions defined in the YAML above affect the Pipeline results.

We are starting from an existing pipeline that is already being edited. To add (or edit) variables click the Variables button in the top right of the screen.

The Variables pop out will show. If we had existing variables they show here. Click the New variable button to add a new variable.

We are adding a variable that will control the build of WebApp2 called BuildWebApp2 that defaults to the value of true. Also, make sure and check the Let user override this value when running this pipeline checkbox to allow us to edit this variable when doing a run of the pipeline. Then click the OK button.

Back on the Variables dialog click the Save button.

Edit Variables When Starting a Pipeline

Now that our Pipeline has a variable when running the Pipeline under Advanced options you will see the Variables section showing that our Pipeline has 1 variable defined. Click Variables to view/edit the variables that will be used for this run of the Pipeline.

From the Variables section, you will see a list of the defined variables as well as an option to add new variables that will exist only for this run of the Pipeline. Click on the BuildWebApp2 variable to edit the value that will be used for this run of the Pipeline.

From the Update variable dialog, you can change the value of the variable. When done click the Update button.

Pipeline Results from Sample YAML

The following is what our sample Pipeline looks like when queued with the BuildWebApp2 variable set to false. As you can see the job will be skipped.

Next is the completed results of the Pipeline run. You can see that the Build Dependent Job was skipped as well since both Build WebApp1 and Build WebApp2 must complete successfully before it will run.

Changing the BuildWebApp2 variable back to true and running the Pipeline again results in all the jobs running successfully.

Wrapping Up

Hopefully, this has helped introduce you to some of the ways you can control your Pipelines. As with everything else Azure DevOps related things are changing a lot and new options are popping up all the time. For example, while writing this post the team just announced Runtime Parameters which look like a much better option than variables for values that frequently vary between Pipeline runs.

Azure DevOps Pipelines: Use YAML Across Repos

In last week’s post, we refactored some YAML that was reusable into a new file. This post is going to cover moving that same reusable YAML to a new repo and then using it in our existing sample repo. This post is going to build on the Azure DevOps project created in previous posts if you are just joining this series check out the previous posts to find out how the project has progressed.

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

Create a New Repository

First, we need to create a new repository that will be used to share the YAML in question. Using the Repos section of Azure DevOps as a starting point you click the dropdown with the currently selected repo name, Playground in this example, and then click New repository.

You will be presented with a dialog where you will need to enter the Repository name and any other of the options you want to configure. In this example, we are naming the repo Shared and adding a Git ignore file for Visual Studio. When done click the Create button.

The following steps should be taken on the new Shared repo. At this point, you could clone the repo and do the rest of the steps locally and then push the changes to the repo or you can use the web interface to make all the change which is the route this post is going to show. Either way, you go it shouldn’t be too hard to adapt the steps. For the web interface to add a new file in the root of the repo click the three-dot menu to the right of the repo name and then select New and then File.

The next prompt will ask for a file name, I’m using buildCoreWebProject.yml. Click Create to continue.

You will land in the file editor. Copy and paste the code out of build.yml that we were using from the previous post into the new file. The following is the full YAML from build.yml for reference.

parameters:
- name: buildConfiguration
  type: string
  default: 'Release'
- name: project
  type: string
  default: ''
- name: artifactName
  type: string
  default: ''

steps:
  - task: [email protected]
    displayName: 'Use .NET 3.1.x'
    inputs:
      packageType: 'sdk'
      version: '3.1.x'

  - task: [email protected]
    displayName: 'Build'
    inputs:
      command: 'build'
      projects: '**/${{ parameters.project }}'
      arguments: '--configuration ${{ parameters.buildConfiguration }}' 
  
  - task: [email protected]
    displayName: 'Publish Application'
    inputs:
      command: 'publish'
      publishWebProjects: false
      projects: '**/${{ parameters.project }}'
      arguments: '--configuration ${{ parameters.buildConfiguration }} --output $(Build.ArtifactStagingDirectory)'

  - task: [email protected]
    displayName: 'Publish Artifacts'
    inputs:
      targetPath: '$(Build.ArtifactStagingDirectory)'
      artifact: ${{ parameters.artifactName }}
      publishLocation: 'pipeline'

Once the code is copied in click the Commit button to save the changes to the master branch.

Switching back to our original repo, Playground in this example, we need to add the Shared repo as a resource for in our azure-pipelines.yml file. The following is the resource deliration for using another Azure DevOps repo. The official docs for check out multiple repositories also show examples with GitHub and Bitbucket. I also found this stackoverflow question helpful.

resources: 
  repositories: 
  - repository: Shared 
    name: Playground/Shared 
    type: git 
    ref: master #branch name

Shared on the repository line is the name we will be using when referencing a file out of the Shared repo. Name is the Azure DevOps project and repo name. Type is the repo type which is Git in our case. Finally, ref is the branch name from the Shared repo that we want to use. Now that we have access to the files from the Shared repo we can use its buildCoreWebProject.yml instead of the local build.yml as a template.

Before:
  - template: build.yml
    parameters:
      buildConFiguration: $(buildConfiguration)
      project: WebApp1.csproj
      artifactName: WebApp1

After:
  - template: [email protected]
    parameters:
      buildConFiguration: $(buildConfiguration)
      project: WebApp1.csproj
      artifactName: WebApp1

Notice that the only change is on the template line which changed from build.yml to [email protected]. The @Shared on the end of the filename is what tells the pipeline the file’s source is the Shared repo. The following is the full azure-pipeline.yml for reference. The frist job is using the template form the Shared repo and the second one is using a local template.

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:
  - template: [email protected]
    parameters:
      buildConFiguration: $(buildConfiguration)
      project: WebApp1.csproj
      artifactName: WebApp1

- job: WebApp2
  displayName: 'Build WebApp2'
  pool:
    vmImage: 'ubuntu-latest'

  steps:
  - template: build.yml
    parameters:
      buildConFiguration: $(buildConfiguration)
      project: WebApp2.csproj
      artifactName: WebApp2

Wrapping Up

Being able to utilize YAML from different repos can help cut down on duplicated YAML and help keep your pipelines across repos cleaner. As with anything else, this is a useful tool when applied appropriately.