Build, test and deploy a dotnet core website using Cake, TeamCity and Octopus Deploy

I have just completed setting up a simple deploy pipeline for a dotnet core project where I use TeamCity to build the project and run xUnit tests and then Octopus Deploy to deploy.

One of the things I wanted to try out for this project, was to use Cake Build to make a build script. By using Cake, I can set up my entire build process in code, instead of setting up multiple build steps in TeamCity. The advantage is that I can now commit my entire build process to git, and can easily reuse it for my next project.

GitHub logo, the octocat
You will find the code for this tutorial on GitHub

The downside is that Cake is far from plug-and-play, and setting up the build process was a lot more complicated than a normal setup in TeamCity. That is why I will use this blog post to hopefully help others avoid a few of the headaches I got along the way.

The complete project is available on GitHub, have a look at the repository if the code snippets below don’t make sense in isolation.

What’s in the project?

There are a lot of moving parts here, but in short, this is the setup in Visual Studio:

An api project

The project I want to build is a super simple web api made with dotnet core 2.1. If you clone my repository and run it from Visual Studio, you will get a web site running at https://localhost:44360/api/values.

I started out with the dotnet core web api project template in Visual Studio and removed everything that wasn’t necessary to run it.

A test project

The test project consists of a single xUnit test class, WebTests, with two tests; One that is set up to always succeed and one that always fails.

Solution items

In the root directory of the solution you will find the files needed for building. The most important file is build.cake, which defines the build process. To run the Cake script, I also need the Cake dll, so the build.ps1 script is for getting the Cake NuGet package and using it to run build.cake.

What is happening in the build process?

To create a build process with Cake, you set up distinct tasks, and set dependencies between them to tell Cake which order to run the tasks.

These are the tasks I have defined in build.cake:

  • Clean
    • Empty bin and obj directories
  • Test
    • Run all xUnit tests in all test projects
  • Publish
    • Build dll files and move them and static files to a directory ready for deployment
  • Pack
    • Use OctoPack to put the published files into a zip file with correct version number
  • Push
    • Send the generated zip file to Octopus Deploy

Ok, lets get into the details of each task.

Setup

This is the beginning of build.cake:

All the variables in the beginning can be overridden when running the build script from the command line, for example you can change value of target to run only a single task.

You will see later in this guide how they are used by TeamCity.

Task Clean

Delete all bin and obj directories in the solution. I have set up TeamCity to always clean the build directories before building, so this step is skipped by TeamCity.

Task Test

This task finds all projects in directory tests and runs the xUnit tests found there.

Task Publish

The task uses dotnet publish to build and collect all necessary files for running the website on a server.

Note that I’m inserting environment variables here. To be able to use these variables, I also need to add the following code to Web.csproj:

The version number for this project will be 1.2.3, which is hardcoded into Web.csproj, followed by a number inserted during the build process, for example 1.2.3.254. In my build process, I insert the TeamCity build number here.

Task Pack

Octopus Deploy accepts both zip and NuGet files for deployment, but it seemed easiest to go with zip here. The trickiest bit was to get the correct version number (generated in the previous publish step). I ended up getting it from the generated dll file.

Task OctoPush

To make sure I don’t accidentally deploy the package, this step only runs on TeamCity, not if the script is run locally. Again, the trickiest bit is that I don’t really know the version number, so I’m just pushing any zip file in the project directory of the format ProjectName.VersionNumber.zip.

Task Default and method RunTarget()

As you can see, the tasks in a Cake script have dependencies to each other. When you call the method RunTarget(“Default”), the Cake program will go up the chain of dependencies and start running the script from the top. The task you put in the RunTarget() method will always be run last.

For example if you execute RunTarget(“Test”), Cake will run task Clean, then Test and then stop.

Build.ps1

To actually run the Cake script, you need the Cake NuGet package. You can download it manually, but instead of doing that, I download it and run the Cake script using a PowerShell script:

First I set up all the variables I want to insert from the command line or from TeamCity during the build.

To make sure I don’t have several versions of the Cake NuGet package, I delete the tools directory and recreate it.

In the tools directory I create an absolutely minimal dotnet core project file and insert the latest version of the Cake NuGet package into the project structure. This makes dotnet core download Cake.dll

Finally, I run build.cake using the Cake NuGet package.

Set up TeamCity to run build.ps1

In TeamCity, I have a project that automatically fetches new commits from a GitHub repository.

The project contains a single build step, a PowerShell runner, with these settings:

Runner type: PowerShell
Format stderr output as: error
Script: Source code
Script execution mode: Execute .ps1 from external file

Script source:

Project parameters:
OctopusApiKey
OctopusUrl

Get the values of the parameters from your Octopus Deploy installation.

Display test results in TeamCity

Out of the box, TeamCity will run the xUnit tests, and will stop the build process if one of the tests fail. But you will only get a generic error message.

To get a detailed view of which tests have passed and which have failed under the Tests tab in TeamCity, you need to install NuGet package TeamCity.VSTest.TestAdapter in the test project.

Leave a Reply

Your email address will not be published. Required fields are marked *

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