My last few posts have covered my experiences of creating a build once deploy anywhere release pipeline for Azure Cloud Services using Team City and Octopus Deploy. My first post covered some of the various deploy/release models and why you might want to try a build once deploy anywhere model. My second post went into a bit more detail about how I went about packaging my solution in order for it to be consumed by Octopus Deploy. This post covers what I found when configuring Octopus Deploy to deploy these packages.

If you are interesting in learning more about Octopus Deploy I can recommend this podcast from Scott Hanselman. Orchestrating and automating deployments with Octopus Deploy and Damian Brady.

The first thing to mention is that a lot of the hurdles I encountered and lessons I learnt were due to the use of Azure Cloud Services. Whilst the discussion that follows includes a lot of details that are specific to this type of deployment, I hope that there are commonalities that are useful in many different situations.

Deploying a Cloud Service

Deploying a Cloud Service in Octopus Deploy should be straight forwards. After all there is an out of the box deployment step designed for this very purpose. The thing you have to be aware of is how Octopus Deploy applies configuration transforms and setting changes to an already packaged Azure deployment. Essentially in unpacks it, apply transformations to the files and repackages it. So simple so far.

The first thing I tried was getting the step to swap out the packaged ServiceConfiguration.cscfg file with the environment specific one. This is something that is advertised that the deployment step will do. However ,this became the first obstacle to clear.

Whilst you can add rules to tell Octopus Deploy how to transform files in your package by enabling the configuration transforms feature this does not apply to the ServiceConfiguration.cscfg file by default. In fact, it is not really doing a transform. Instead it is swapping the *.cscfg that the build process places in the package with the one you actually want for the environment. So Octopus Deploy looks in the root of the package for a file called ServiceConfiguration.{ENV}.cscfg where ENV is the name of the environment that OD is deploying to, or one called ServiceConfiguration.Cloud.cscfg. If it does not find one, the deployment doesn’t work.

deploy1

So this is the start of the changes needed in the packaging on the build side. Originally I had

<file src="..\MyWebRole\ServiceConfiguration.*.cscfg"
target="ServiceConfiguration" />

Instead I needed

<file src="..\MyWebRole\ServiceConfiguration.*.cscfg"
target="" />

The next problem I had was that my environments in Octopus Deploy did not match the names of my Azure target profiles. So even when it was getting the files on the root of the package the deployment still failed. The only way I could see around this was to ensure these matched. There may be ways around this but I couldn’t find any in the time I had. The next stage involved the transformation of the web.config file. This proved to be the biggest challenge.

Up until now all of the files we were swapping or transforming lived outside of the Azure Cloud Service package. This time the web.config file could be found deep inside a folder structure that is defined by the Azure package itself. Within the outer NuGet package I had copied the transform files into a folder called WebConfigTransforms. Octopus Deploy could not handle this structure automatically. This was hard to find because the deployment was not failing. It is only a missing entry in a log that acts as a clue. OD allows you to define custom transformation rules. What this amount to are expressions that describe where the configuration file is relative to the transform file. In order to this I would have had to understand the file structure that results from OD unpacking the Azure package. Whilst it was possible to discover this it didn’t feel right to me to bake this folder structure into the deployment step. What if this folder structure was to change? Unlikely, but it might.

The configuration transform feature screen hints that you can do something like this

Web.BETA.config => Web.config

However this doesn’t work either. The deployment continues with only the following in the log to tell you something has gone wrong.

The transform pattern Web.BETA.config =&gt; Web.config was not performed due to a missing file or overlapping rule

It was Googling this error that finally surfaced this post from the Octopus Deploy help forum.

http://help.octopusdeploy.com/discussions/questions/5449-cloudservice-nuget-package-structure-for-config-files

This post indicated that is simplest solution was include the transforms in the Azure package by setting the Web.Config’s ‘Copy to Output Directory’ value to ‘Copy always’.

deploy2

Doing this puts the web.config and all of its transforms into the same folder. The Configure Transform step could now apply the transforms automatically. Time to remove the webconfig transform files from the *.nuspec file as there was no longer a need to add them to the NuGet package explicitly.

deploy3

The next stage was to apply the necessary transforms to the ServiceDefinition.csdef file. This can be achieved with the Configure Transforms feature but this required a tiny bit of work. The Configure Transform step doesn’t deal with this type of file automatically so you need a custom transform rule. One like this one.

ServiceDefinitionProfiles\ServiceDefinition.#{Octopus.Environment.Name}.config
=&gt; ServiceDefinition.csdef

.#{Octopus.Environment.Name} is the name of a system variable that gives you the name of the environment you are deploying to.

At this point I had a functioning deployment with all the replacements and transformations working. However, there was one other aspect of my deployment that is worth discussing. This element highlights something that you’ll commonly do as part of your release cycle that you might need to think differently about when working in a Build Once and Deploy Anywhere model. That aspect is running database migrations.

Database Migration

As discussed previously the build step is environment agnostic. Migrations on the other hand are applied to a specific environment. Therefore in a build once deploy anywhere model it does not make sense to have the build step run migrations.

In order for the deployment step to run migrations a number of things that must be in place

  • The assemblies containing the migrations must be packaged and accessible to the deployment server
  • Any scripts used to run the database migrations must be available to the deployment server
  • Any tools required to run the database migrations must be available to the deployment server

If you read the second post in this series you might realise that the answer lies in packaging. In order to make everything available to the deployment server they must be added to the NuGet package.

But first a comment about how Octopus Deploy handles deployment scripts. In the deployment process there are four stages

  • Pre Deploy
  • Deploy
  • Post Deploy
  • Deploy Failed

If the NuGet package contains powershell scripts called PreDeploy.ps1, Deploy.ps1, PostDeploy.ps1 & DeployFailed.ps1 on the root of the package, Octopus Deploy will automatically run them at the correct stage. This help article explains it in more detail. In order to have Octopus Deploy run a migration script after a success deployment of a Cloud Service, add the migration script to the root of the package and call it PostDeploy.ps1.

Now the script is not the end of the story. You’ll also need the Migration assemblies and if you are using something like FluentMigrator you’ll need that too. I ensured all of this was in the package. I don’t rely on the tool being on the deployment server in a specific location. If you to that you are coupling the script and server configuration together. You may as well hard code the script in the deployment project configuration. If you want the script to be part of the deployment package it should be stand alone. The tooling should be packaged in a known location and the script can refer to the tool in relative terms.

<!-- Package deployment script - Must be on the root -->
<file src="DeployScripts\Migrations\*.*" target="" />

<!-- Add migration DLLs in the package so they are available on the Deploy Server -->
<file src="Migrations\bin\$ConfigurationName$\Migrations.dll" target="migrationdlls" />

<!-- Bring across the Migration Tool to ensure that we can run the migration -->
<file src="packages\FluentMigrator.Tools.1.6.0\tools\AnyCPU\40\*.*" target="migrationtool" />

The powershell script itself has a line like this in it

$migrationExePath = (join-path $currentDirectory "migrationtool\Migrate.exe")

This is what it took to create a working release pipeline. I have simplified or skipped over things to keep these posts brief but they give a flavour of what the typical pain points are. I hope you find them useful.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s