Octopus deployments with TeamCity for multiple forks of the same git repository

Paul has good guidance to get you going here.

My requirement was to have deployments for various forks of Git repositories enabled via Octopus. Octopus will interrogate the TC nuget feed for available packages, so how does one get it to differentiate between packages of different forks of the same git repo?

The problem is that the packages have the same name, so Octopus could correctly be configured to pull in the package for deployment, but it could be a package generated by the wrong fork. So, one has to somehow get the package named uniquely per fork. Right, so how?

Puzzle piece 1: The name of a nuget package is specified by the ID in it’s nuspec file. Here’s a link to the nuspec reference.

Puzzle piece 2: Octopus makes use of Octopack to generate slightly modified nuget packages for deployment. OctoPack consumes an OctoPack.targets msbuild file to integrate with the build process.

The octopack.targets contains these properties:

<!–
Configuration properties – you can override these from the command line
–>
<PropertyGroup>
  <OctopusProjectRoot Condition=”‘$(OctopusProjectRoot)’ == ””>$(MSBuildProjectDirectory.TrimEnd(”))</OctopusProjectRoot>
  <OctopusPackageConfiguration Condition=”‘$(OctopusPackageConfiguration)’ == ””>Release</OctopusPackageConfiguration>
  <OctopusNuSpecFileName Condition=”‘$(OctopusNuSpecFileName)’ == ””>$(MSBuildProjectName.Replace(‘.csproj’, ”).Replace(‘.vbproj’, ”)).nuspec</OctopusNuSpecFileName>
  <OctopusTemporaryDirectory Condition=”‘$(OctopusTemporaryDirectory)’ == ””>$(OutputPath.TrimEnd(”))NuGet-temp</OctopusTemporaryDirectory>
  <OctopusWebConfigFile Condition=”‘$(OctopusWebConfigFile)’ == ””>$(OctopusProjectRoot)Web.config</OctopusWebConfigFile>
  <OctopusOutputDirectory Condition=”‘$(OctopusOutputDirectory)’ == ””>$(OutputPath)</OctopusOutputDirectory>
  <OctopusPublishPackageToFileShare Condition=”‘$(OctopusPublishPackageToFileShare)’ == ””></OctopusPublishPackageToFileShare>
  <OctopusPublishPackageToHttp Condition=”‘$(OctopusPublishPackageToHttp)’ == ””></OctopusPublishPackageToHttp>
  <OctopusPublishApiKey Condition=”‘$(OctopusPublishApiKey)’ == ””></OctopusPublishApiKey>
</PropertyGroup>

Note the OctopusNuSpecFileName property. We can use it to specify the name of the nuspec file for a project.

Puzzle piece 3: Get TeamCity to pass the correct parameter to octopack for the relevant build

 

Solution:

I configured separate CI and Packaging projects in TeamCity.

image

The Packaging project has an snapshot dependency on the CI build.

image

The Packaging project has the following Artifact Paths (in General Settings):

image

This simply tells TeamCity to copy any .nupkg files to a Packages folder in the build artifacts.

Build Steps:

image

 

Cleanup

The first build step deletes any previous .nukpg files in case some are hanging around with the following command line:

del /S /F *.nupkg

 

Package

The actual packaging MSBuild steps follow the guidance provided by Paul, with the only extra bit of information provided being the OctopusNuSpecFileName parameter as a a command line parameter:

OctopusNuSpecFileName=MyFork.nuspec

The parameter as specified above will result in a MyFork.nupkg package being generated, and because of the artifact paths defined, it will be copied to the Packages folder in the artfacts produced by the project.

 

Octopus Configuration

The last step is to configure the various deployment projects in Octopus. All that needs to be done is to consume the correct nuget package from TeamCity for your deployments. I have configured a project for each fork’s deployment.

BDD Integration Tests

Having played with a few of the BDD Frameworks out there, we decided to go with StoryQ [http://storyq.codeplex.com/]. We looked at SpecFlow (SF) as well but had major difficulties in getting it to play nice with our “transactional pattern” for dealing with data setup per test.

We basically initiate a transaction (TransactionScope) in our tests, in which we perform data setup, and run the test. The transaction never gets committed so data changes are automatically rolled back, avoiding collisions with other tests. I do not want to move away from this pattern as it’s very effective and easy to understand and bootstrap into some basic infrastructure.

StoryQ does not create a huge abstraction away from the underlying test infrastructure like SF does. With SF you are writing to Cucumber spec and and it generates the test fixture and the related scenario file for you. You don’t deal directly with the tests themselves which was where our problem was realised. There are hooks for performing actions at various stages of the test lifecycle but nothing that enabled us to initiate a discrete transaction per test. So, no go.

StoryQ lives in your own test fixture, so you can do whatever you want. You define the test using the preferred Given-When-Then structure which is made possible with a neat little fluent interface exposed by StoryQ:

[Test]
public void AddSlide()
{
    using (TransactionScopeHelper.Transaction())
    {
        new Story(“Add Slide to Case”)
            .InOrderTo(“Add a slide to a case”)
            .AsA(“Valid User”)
            .IWant(“The slide to be added to the case”)
            .WithScenario(“A valid slide”)
            .Given(ValidSlideDetailsAreEntered)
            .When(TheAddSlideButtonIsPressed)
            .Then(TheSlideShouldBeAddedToTheCase)
            .ExecuteWithReport(MethodBase.GetCurrentMethod());
    }
}

Your outcome assertions are in the function called when .Then is executed; too easy. You can place assertions into the Given and When steps as required.

You also get the pretty output which reads like English and can be given to testers / domain experts for validation:

Story is Delete Slide from Case
  In order to Delete a slide from a case
  As a Valid User
  I want The slide to be removed from to the case

      With scenario A valid unprocessed slide
        Given an unprocessed slide is selected    => Passed
        When the delete slide button is pressed   => Passed
        Then the slide should be deleted          => Passed

 

We have structured the tests like this:

Integration Test Project

   –> Stories

       –> Folder For Story

           –> File for each scenario

image