YAML-defined CI/CD for ASP .NET Core

By Shahed C on June 24, 2019

This is the twenty-fifth of a series of posts on ASP .NET Core in 2019. In this series, we’ll cover 26 topics over a span of 26 weeks from January through June 2019, titled A-Z of ASP .NET Core!

ASPNETCoreLogo-300x267 A – Z of ASP .NET Core!

In this Article:

Y is for YAML-defined CI/CD for ASP .NET Core

If you haven’t heard of it yet, YAML is yet another markup language. No really, it is. YAML literally stands for Yet Another Markup Language. If you need a reference for YAML syntax and how it applies to Azure DevOps Pipelines, check out the official docs:

In the context of Azure DevOps, you can use Azure Pipelines with YAML to make it easier for you set up a CI/CD pipeline for Continuous Integration and Continuous Deployment. This includes steps to build and deploy your app. Pipelines consist of stages, which consist of jobs, which consists of steps. Each step could be a script or task. In addition to these options, a step can also be a reference to an external template to make it easier to create your pipelines.

YAML-Syntax

This article will refer to the following sample code on GitHub, which contains a Core 2.2 web project and a sample YAML file:

Web Web Project with YAML Pipeline : https://github.com/shahedc/AspNetCoreWithPipeline

Getting Started With Pipelines

To get started with Azure Pipelines in Azure DevOps:

  1. Log in at: https://dev.azure.com
  2. Create a Project for your Organization
  3. Add a new Build Pipeline under Pipelines | Builds
  4. Connect to your code location, e.g. GitHub repo
  5. Select your repo, e.g. a specific GitHub repository
  6. Configure your YAML
  7. Review your YAML and Run it

From here on forward, you may come back to your YAML here, edit it, save it, and run as necessary. You’ll even have the option to commit your YAML file “azure-pipelines.yml” into your repo, either in the master branch or in a separate branch (to be submitted as a Pull Request that can be merged).

YAML-New-Pipeline

If you need more help getting started, check out the official docs and Build 2019 content at:


To add pre-written snippets to your YAML, you may use the Task Assistant side panel to insert a snippet directly into your YAML file. This includes tasks for .NET Core builds, Azure App Service deployment and more.

YAML-Task-Assistant

OS/Environment and Runtime

From the sample repo, take a look at the sample YAML file “azure-pipelines.yml“. Near the top, there is a definition for a “pool” with a “vmImage” set to ‘windows-2019’.

pool:
 vmImage: 'windows-2019'

If I had started off with the default YAML pipeline configuration for a .NET Core project, I would probably get a vmImage value set to ‘ubuntu-latest’. This is just one of many possible values. From the official docs on Microsoft-hosted agents, we can see that Microsoft’s agent pool provides at least the following VM images across multiple platforms, e.g.

  • Visual Studio 2019 Preview on Windows Server 2019 (windows-2019)
  • Visual Studio 2017 on Windows Server 2016 (vs2017-win2016)
  • Visual Studio 2015 on Windows Server 2012R2 (vs2015-win2012r2)
  • Windows Server 1803 (win1803) – for running Windows containers
  • macOS X Mojave 10.14 (macOS-10.14)
  • macOS X High Sierra 10.13 (macOS-10.13)
  • Ubuntu 16.04 (ubuntu-16.04)

In addition to the OS/Environment, you can also set the .NET Core runtime version. This may come in handy if you need to explicitly set the runtime for your project.

steps:
- task: DotNetCoreInstaller@0
 inputs:
 version: '2.2.0'

Restore and Build

Once you’ve set up your OS/environment and runtime, you can restore and build your project. To build a specific configuration by name, you can set up a variable first to define the build configuration, and then pass in the variable name to the build step.

variables:
 buildConfiguration: 'Release'

steps:
- script: dotnet restore

- script: dotnet build --configuration $(buildConfiguration)
 displayName: 'dotnet build $(buildConfiguration)'

In the above snippet, the buildConfiguration is set to ‘Release’ so that the project is built for its ‘Release’ configuration. The displayName is a friendly name in a text string (for any step) that may include variable names as well. This is useful for observing logs and messages during troubleshooting and inspection.

Note the use of script steps to make use of dotnet commands with parameters you may already be familiar with, if you’ve been using .NET Core CLI Commands. This makes it easier to run steps without having to spell everything out. From the official docs, here are some more detailed steps for restore and build, if you wish to customize your steps and tasks further:

steps:
- task: DotNetCoreCLI@2
 inputs:
 command: restore
 projects: '**/*.csproj'
 feedsToUse: config
 nugetConfigPath: NuGet.config 
 externalFeedCredentials: <Name of the NuGet service connection>

Note that you can set your own values for an external NuGet feed to restore dependencies for your project. Once restored, you may also customize your build steps/tasks.

steps:
- task: DotNetCoreCLI@2
 displayName: Build
 inputs:
 command: build
 projects: '**/*.csproj'
 arguments: '--configuration Release'

Unit Testing and Code Coverage

Although unit testing is not required for a project to be compiled and deployed, it is absolutely essential for any real-world application. In addition to running unit tests, you may also want to measure your code coverage for those unit tests. All these are possible via YAML configuration.

From the official docs, here is a snippet to run your unit tests, that is equivalent to a “dotnet test” command for your project:

steps:
- task: DotNetCoreCLI@2
 inputs:
 command: test
 projects: '**/*Tests/*.csproj'
 arguments: '--configuration $(buildConfiguration)'

Also, here is another snippet to collect code coverage:

steps:
- task: DotNetCoreCLI@2
 inputs:
 command: test
 projects: '**/*Tests/*.csproj'
 arguments: '--configuration $(buildConfiguration) --collect "Code coverage"'

Once again, the above snippet uses the “dotnet test” command, but also adds the –collect option to enable the data collector for your test run. The text string value that follows is a friendly name that you can set for the data collector. For more information on “dotnet test” and its options, check out the docs at:

Package and Deploy

Finally, it’s time to package and deploy your application. In this example, I am deploying my web app to Azure App Service.

- task: DotNetCoreCLI@2
 inputs:
 command: publish
 publishWebProjects: True
 arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
 zipAfterPublish: True 

- task: PublishBuildArtifacts@1
 displayName: 'publish artifacts'

The above snippet runs a “dotnet publlish” command with the proper configuration setting, followed by an output location, e.g. Build.ArtifactStagingDirectory. The value for the output location is one of many predefined build/system variables, e.g. System.DefaultWorkingDirectory, Build.StagingDirectory, Build.ArtifactStagingDirectory, etc. You can find out more about these variables from the official docs:

The PublishBuildArtifacts task uploads the package to a file container, ready for deployment. After your artifacts are ready, it’s time to deploy your web app to Azure, e.g. Azure App Service.

- task: AzureRmWebAppDeployment@4
 inputs:
 ConnectionType: 'AzureRM'
 azureSubscription: '<REPLACE_AZURE_SUBSCRIPTION_NAME_(ID)>'
 appType: 'webApp'
 WebAppName: 'WebProjectForPipelines'
 packageForLinux: '$(System.ArtifactsDirectory)/**/*.zip'
 enableCustomDeployment: true
 DeploymentType: 'webDeploy'

The above snippet runs msdeploy.exe using the previously-created zipped package. Note that there is a placeholder text for the Azure Subscription ID. If you use the Task Assistant panel to add a “Azure App Service Deploy” snippet, you will be prompted to select your Azure Subscription, and a Web App location to deploy to, including deployment slots if necessary. Note that the DeploymentType actually defaults to ‘webDeploy’ so setting the value may not be necessary. However, if UseWebDeploy (optional) is set to true, the DeploymentType is required.

You may use the Azure DevOps portal to inspect the progress of each step and troubleshoot any failed steps. You can also drill down into each step to see the commands that are running in the background, followed by any console messages.

YAML-Pipeline-Success

NOTE: to set up a release pipeline with multiple stages and optional approval conditions, check out the official docs at:

Triggers, Tips & Tricks

Now that you’ve set up your pipeline, how does this all get triggered? If you’ve taken a look at the sample YAML file, you will notice that the first command includes a trigger, followed by the word “master”. This ensures that the pipeline will be triggered every time code is pushed to the corresponding code repository’s master branch. When using a template upon creating the YAML file, this trigger should be automatically included for you.

trigger:
- master

To include more triggers, you may specify triggers for specific branches to include or exclude.

trigger:
 branches:
 include:
 - master
 - releases/*
 exclude:
 - releases/old*

Finally here are some tips and tricks when using YAML to set up CI/CD using Azure Pipelines:

  • Snippets: when you use the Task Assistant panel to add snippets into your YAML, be careful where you are adding each snippet. It will insert it wherever your cursor is positioned, so make sure you’ve clicked into the correction location before inserting anything.
  • Order of tasks and steps: Verify that you’ve inserted (or typed) your tasks and steps in the correct order. For example: if you try to deploy an app before publishing it, you will get an error.
  • Indentation: Whether you’re typing your YAML or using the snippets (or some other tool),  use proper indentation. You will get syntax errors of the steps and tasks aren’t indented correctly.
  • Proper Runtime/OS: Assign the proper values for the desired runtime, environment and operating system.
  • Publish Artifacts: Don’t forget to publish your artifacts before attempting to deploy the build.
  • Artifacts location: Specify the proper location(s) for artifacts when needed.
  • Authorize Permissions: When connecting your Azure Pipeline to your code repository (e.g. GitHub repo) and deployment location (e.g. Azure App Service), you will be prompted to authorize the appropriate permissions. Be aware of what permissions you’re granting.
  • Private vs Public: Both your Project and your Repo can be private or public. If you try to mix and match a public Project with a private Repo, you will get the following warning message: “You selected a private repository, but this is a public project. Go to project settings to change the visibility of the project.” 

References

 

4 thoughts on “YAML-defined CI/CD for ASP .NET Core

  1. Pingback: Dew Drop – June 25, 2019 (#2986) | Morning Dew

  2. Pingback: Zero-Downtime* Web Apps for ASP .NET Core | Wake Up And Code!

  3. Frédéric

    If I’m right this article requires an App Service Plan and a Web App already created in Azure? It would be a great addition to deploy these as well.

    Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.