From Dev to Production: Managing Continuous Deployment with Octopus Deploy

When it comes to continuous deployment, automation is key. Octopus deploy provides an automated solution for deploying your application, as well as performing post-deployment operations such as testing.

The purpose of this article is not to detail the features Octopus provides, but here are some highlights. For more information, check out the great documentation provided by the folks at Octopus, https://octopusdeploy.com.

  • Deployment solution for .NET applications.
  • Deploys on-premises or to the cloud.
  • Built in integration with TeamCity and TFS.

Other things I won’t cover in this article is server and tentacle installation. I will say that it is ridiculously easy.  Even a software tester can do it! ;).

Now, on to what I will cover. The goal of this article is to provide the reader with enough information to get a continuous deployment process built and running using Octopus deploy.

I’ve already setup a demo solution to showcase Octopus’ functionality. You can do the same easily. It’s simply the default .NET web API project with a cloud service, a UI test project and a performance test project.

Setup OctoPack

OctoPack is a tool that packages your application. It produces an Octopus compatible NuGet package of your application that can then be sent to the Octopus server.

Add it to your test projects via NuGet.

Configure .targets

Adding the OctoPack NuGet package creates some files in the packages folder of your solution. Navigate to packages/OctoPack.#.#.##/tools and open the OctoPack.targets file for editing.

We need to give OctoPack some information in order for it to know where to send the packages. Make the following changes:

<OctoPackPublishPackageToHttp Condition="'$(OctoPackPublishPackageToHttp)' == ''">http://youroctopusserver.com/nuget/packages</OctoPackPublishPackageToHttp>
<OctoPackPublishApiKey Condition="'$(OctoPackPublishApiKey)' == ''">API-YourAPIKey</OctoPackPublishApiKey>
<OctoPackPackageVersion Condition="'$(OctoPackPackageVersion)' == ''">$([System.DateTime]::UtcNow.ToString(`yyyy.MM.dd.HHmm`))</OctoPackPackageVersion>

To prevent OctoPack from hooking into the AfterBuild target, we should comment out the following lines.

<!--<PropertyGroup>
<BuildDependsOn>
  $(BuildDependsOn);
  OctoPack
</BuildDependsOn>
</PropertyGroup>-->

Next, we need to instruct the cloud service and test projects to execute OctoPack at the appropriate times.

Add the following to the end of the cloud service project. This instructs the cloud service project to package after the CorePublish task.

<Import Project="..\packages\OctoPack.3.0.42\tools\OctoPack.targets" Condition="Exists('..\packages\OctoPack.3.0.42\tools\OctoPack.targets')" />
<Target Name="EnsureOctoPackImported" BeforeTargets="BeforeBuild" Condition="'$(OctoPackImported)' == ''">
  <Error Condition="!Exists('..\packages\OctoPack.3.0.42\tools\OctoPack.targets') And ('$(RunOctoPack)' != '' And $(RunOctoPack))" Text="You are trying to build with OctoPack, but the NuGet targets file that OctoPack depends on is not available on this computer. This is probably because the OctoPack package has not been committed to source control, or NuGet Package Restore is not enabled. Please enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=317567." HelpKeyword="BCLBUILD2001" />
  <Error Condition="Exists('..\packages\OctoPack.3.0.42\tools\OctoPack.targets') And ('$(RunOctoPack)' != '' And $(RunOctoPack))" Text="OctoPack cannot be run because NuGet packages were restored prior to the build running, and the targets file was unavailable when the build started. Please build the project again to include these packages in the build. You may also need to make sure that your build server does not delete packages prior to each build. For more information, see http://go.microsoft.com/fwlink/?LinkID=317568." HelpKeyword="BCLBUILD2002" />
</Target>
<Target Name="ExecuteOctoPack" Condition="$(RunOctoPack)" AfterTargets="CorePublish">
  <CallTarget Targets="OctoPack" />
</Target>

Add the following to the end of the test projects. This instructs the test projects to package after the Build task.

<Import Project="..\packages\OctoPack.3.0.42\tools\OctoPack.targets" Condition="Exists('..\packages\OctoPack.3.0.42\tools\OctoPack.targets')" />
<Target Name="EnsureOctoPackImported" BeforeTargets="BeforeBuild" Condition="'$(OctoPackImported)' == ''">
  <Error Condition="!Exists('..\packages\OctoPack.3.0.42\tools\OctoPack.targets') And ('$(RunOctoPack)' != '' And $(RunOctoPack))" Text="You are trying to build with OctoPack, but the NuGet targets file that OctoPack depends on is not available on this computer. This is probably because the OctoPack package has not been committed to source control, or NuGet Package Restore is not enabled. Please enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=317567." HelpKeyword="BCLBUILD2001" />
  <Error Condition="Exists('..\packages\OctoPack.3.0.42\tools\OctoPack.targets') And ('$(RunOctoPack)' != '' And $(RunOctoPack))" Text="OctoPack cannot be run because NuGet packages were restored prior to the build running, and the targets file was unavailable when the build started. Please build the project again to include these packages in the build. You may also need to make sure that your build server does not delete packages prior to each build. For more information, see http://go.microsoft.com/fwlink/?LinkID=317568." HelpKeyword="BCLBUILD2002" />
</Target>
<Target Name="AfterBuild" Condition="$(RunOctoPack)">
  <CallTarget Targets="OctoPack" />
</Target>

We also need a NuSpec file to tell OctoPack what files to package in the cloud service Project. Create a file named OctopusDemo.nuspec in the root folder of the cloud service project and add the following text.

<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
  <metadata>
    <id>OctopusDemo</id>
    <version>1.0</version>
    <authors>Dustin Iser</authors>
    <description>Cloud Service Package</description>
  </metadata>
  <files>
    <file src="bin\Debug\app.publish\*.*" target="" />
  </files>
</package>

We can now execute the following command from the command line and our application and it’s tests will be packaged and copied to the Octopus server.

msbuild OctopusDemo.sln /t:Publish;Build /p:RunOctoPack=true
Build a Deployment Process

Now we need to create a deployment process to utilize our newly created packages.

  • If you haven’t already, create a project named OctopusDemo on the Octopus server.
  • Once in the project, click Process on the left hand side.
  • Click “Add step” to create the deployment step.
    • Click Deploy to Windows Azure.
    • Enter the following information:
      • Step name
      • Machine roles – The role that will perform the step.
      • NuGet package – “OctopusDemo”
      • Subscription Id – Your Azure subsription id.
      • Cloud server – The name of the cloud service you would like to deploy to.
      • Storage account – The name of the storage account to use.
      • Slot – “Production” or “Staging”
      • Environments – The names of the environments you would like to deploy to.
    • Click Save.

Next we should create a release and deploy it.

  • Click “Create release” in the upper right corner.
  • Give the release a version number and insure the latest version of the OctopusDemo package is selected for the deployment step.
  • Click Save.
  • Click “Deploy to Development”.
  • Click “Deploy now”.

We can now use Octopus to deploy our application manually, but continuous deployment this is not. Let’s make the deployment process completely automated and triggered on the build process.

Implement OctopusTools

Octopus tools is a NuGet package that provides the commands necessary for automatically creating and deploying Octopus packages.

Right click the OctopusDemo solution and add OctopusTools to the solution via the Manage NuGet Packages dialog.

Add the following target to the OctoPack.targets file.

<Target Name="AfterOctoPack">
  <PropertyGroup>
    <OctopusTool>..\packages\OctopusTools.2.6.3.59\octo.exe</OctopusTool>
    <OctopusAPICommand>create-release</OctopusAPICommand>
  </PropertyGroup>

  <Exec Command="$(OctopusTool) $(OctopusAPICommand) --debug -apiKey=$(OctoPackPublishApiKey) --server=$(OctoPackPublishPackageToHttp)api --project=$(OctoPackProjectName) --deployto=Development --version=$(OctoPackPackageVersion)" />
</Target>

Also adjust the cloud service project to also run AfterOctoPack post-publish.

<Target Name="ExecuteOctoPack" Condition="$(RunOctoPack)" AfterTargets="CorePublish">
  <CallTarget Targets="OctoPack" />
  <CallTarget Targets="AfterOctoPack" />
</Target>

Now when we execute a build, a release is created and deployed automatically. Next, lets setup some testing.

Automated Tests

From the Octopus UI, navigate to the OctopusDemo Process page. Here we will add a step to automatically test the application post-deployment.

  • Click Add step.
  • Select “Deploy a NuGet package”.
    • Enter the following information.
      • A step name.
      • A machine role.
      • The NuGet package id, OctopusDemoUITests.
      • Environments.
    • Click Save.
  • Click the button in the upper right corner of the step you just created and select “Add child step”.
  • Select “Run a PowerShell script”.
    • Enter the following information.
      • A step name.
      • The PowerShell script.
      • Environments.
cd $OctopusParameters['Octopus.Action[Deploy UI Tests].Output.Package.InstallationDirectoryPath']
$testDLL = "OctopusDemoUITests.dll"
$fs = New-Object -ComObject Scripting.FileSystemObject
$f = $fs.GetFile("C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\MSTest.exe")
$mstestPath = $f.shortpath
$arguments = " /testcontainer:" + $testDLL
Invoke-Expression "$mstestPath $arguments"

Navigate to the Variables screen and create a variable named “URL”. Set it to “http://dustinsoctopusdemo.cloudapp.net/” and set its scope to Development. Now when we deploy our tests, the URL variable in the app config will be replaced with the Octopus variable.

Now when we build our application deployment and automated UI tests are ran. Next, we can utilize Lifecycles to automate performance testing.

Lifecycles
  • On the Octopus top bar, click Library.
  • On the left hand side, select Lifecycles.
  • Select the default lifecycle to edit it.
  • Set it up so that phase one contains the development environment and phase 2 contains the performance environment and phase 2 is deployed to automatically.

Now when a deployment to the development environment is successful, a deployment to the performance environment will automatically be triggered.

To run performance tests automatically, we’ll create a new step in the deployment process called Deploy Performance Tests.

  • Create a step that deploys the performance tests the same way we did with the UI tests except set the environment to Performance.
  • Use the following PowerShell script instead.
cd $OctopusParameters['Octopus.Action[Deploy Performance Tests].Output.Package.InstallationDirectoryPath']
$testDLL = "ProjectionPerf.DataMangement.dll"
$fs = New-Object -ComObject Scripting.FileSystemObject
$f = $fs.GetFile("C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\MSTest.exe")
$mstestPath = $f.shortpath
$loadTests = Get-ChildItem . *.loadtest
foreach ($loadTest in $loadTests) {
    $arguments = " /testcontainer:" + $loadTest
    Invoke-Expression "$mstestPath $arguments"
}
Conclusion

Octopus deploy provides a fun, easy way of automating your continuous deployment process. Providing automatic feedback regarding the quality of your code is a great way of streamlining your development process. Combining this with the use of lifecycles gives you the ability of gating your deployments in such a way that once a build is determined to be of high quality it can be deployed to the next phase, possibly all the way to production.