Concourse Pipelines

To build .NET Framework applications with Concourse, the .NET framework and build tools must first be installed and configured on a Concourse Windows worker. Refer to the Concourse documentation for running a worker node for stand alone workers, and Concourse for PCF for bosh managed workers.

The Concourse pipeline and build script can be reused to build and publish .NET applications to PCF. Feel free to use it as reference for your own custom build script, or use it as-is.

Pipeline flow

The sequence of events is:

  • Get, compile and unit test (xunit) the solution
  • Deploy to PCF
  • Run integration (smoke) tests using postman

.NET Project Setup

CI headless builds for .NET applications are based on msbuild.exe. We are providing a reusable build script sample that:

  • installs Nuget to retrive nuget packages
  • invokes msbuild to compile the solution
  • invokes xunit runner to test the applications
  • install web.target and uses Web Publishing profile to publish (assemble) all required assets (dlls, svc, pages etc) to some file directory
  • Push all prepared (published) assets to PCF

To use this script as-is in consistent manner the Visual Studio solution must adhere to the following requirements:

  • Have Test configuration - builds projects required for unit testing
  • Have Release configuration - build projects that will be deployed to PCF
  • Web based projects have Publishing profile - “CF” - which copies all assets to file directory
  • Project has .nuget directory with nuget.exe and nuget.config pointing to repository for package installs

Concourse Tasks

The full sample pipeline and build script are downloadable, but let’s go into more detail below.

Building and Running Unit Tests

Unit testing tasks running on windows invokes build.ps1 in test mode. This Concourse job does nothing more than invoke the build script’s test target.

jobs:
- name: unit-test
  public: true
  plan:
  - get: code
    trigger: true
  - task: build-and-test
    config:
      platform: windows
      run:
        path: powershell
        args:
        - ./build.ps1
        - -mode test -targetFramework 4.6.1
        dir: code
      inputs:
      - name: code

The build script’s test mode handles the majority of the work, first building the solution via msbuild.exe using the specified framework version passed in from the Concourse task.

function Build-Solution($configuration) {
   $code = -1
   . {
       Configure-Publish

       $frameworkVer, $frameworkPath = Get-TargetFramework
   	$frameworkParam = ""
       if($frameworkVer -ne $DefaultFramework) {
           $frameworkParam = "/p`:FrameworkPathOverride=`"$frameworkPath`""
       }

       $app = "$MsBuildApp /m /v:normal /p`:Platform=$Architecture /p`:Configuration=$configuration /nr:false $publish $tools $frameworkParam"
       Write-Host "Running the build script: $app" -ForegroundColor Green
       Invoke-Expression "$app" | Write-Host
       $code = $LastExitCode
   } | Out-Null

   if($code -ne 0) {
       Write-Host "Build FAILED." -ForegroundColor Red
   }
   else{
       Write-Host "Build SUCCESS." -ForegroundColor Green
   }

   $code
}

Then the script handles installing the xunit console runner and then executing xunit against each found test project. A test project is any found DLL file name that ends with the word Tests.

function main {
    Write-Banner

    $buildConfig = if ($(Get-Mode) -eq 'test') {'Test'} Else {'Release'}
    $buildResult = Build-Solution $buildConfig
    
    if($buildResult -ne 0) {
        Write-Host "Build failed, aborting..." -ForegroundColor Red
        Exit $buildResult
    } 

    if($(Get-Mode) -eq 'test') {
        Write-Host "Starting unit test execution" -ForegroundColor Green
        NuGet-Install 'xunit.runner.console' $XUnitVersion
        
        $failedUnitTests = 0
        
        #Get the matching test assemblies, ensure only bin and the target architecture are selected
        $testfiles = Get-ChildItem . -recurse  | where {$_.BaseName.EndsWith("Tests") -and $_.Extension -eq ".dll" `
            -and $_.FullName -match "\\bin\\" -and $_.FullName -match "$Architecture"  }

        #Execute unit tests in all assemblies, continue even in case of error, as it provides more context
        foreach($UnitTestDll in $testfiles) {
            Write-Host "Found Test: $($UnitTestDll.FullName)" -ForegroundColor Yellow
            Invoke-Expression "$XUnitApp $($UnitTestDll.FullName)"

            if( $LastExitCode -ne 0){
                $failedUnitTests++
                Write-Host "One or more tests in assembly FAILED" -ForegroundColor Red
            } 
            else
            {
                Write-Host "All tests in assembly passed" -ForegroundColor Green
            }
        }

        if($failedUnitTests -ne 0) {
            Write-Host "Unit testing for $(Get-Mode) configuration FAILED." -ForegroundColor Red
            
        } else {
            Write-Host "Unit testing for $(Get-Mode) configuration completed successfully." -ForegroundColor Green
        }
        Exit $failedUnitTests
    } 
}

Publish Application to PCF

The Concourse publish job and tasks delegate the majority of the work to the build script, however this time running it in publish mode.

- name: publish
  public: true
  plan:
  - get: pipeline
    trigger: true
  - get: code
    passed:
    - unit-test
    trigger: true
  - task: build-and-publish
    config:
      platform: windows
      run:
        path: powershell
        args:
        - ./build.ps1
        - -mode publish
        dir: code
      inputs:
      - name: code
      outputs:
      - name: publish
  - put: pcf
    params:
      manifest: pipeline/dev/manifest.yml
      path: publish/{{publish_profile_directory}}

NOTE - Using cf-resource might be a better way to solve this, however it’s unclear if Windows workers are supported.

Powershell script to set publish profile which is invoked by msbuild.exe

function Configure-Publish {
    . {

        <#
            Always download this package and set up the override for the 
            VSToolsPath as there may be dependencies in the MSBuild file on a 
            specific version on VisualStudio
        #>
        Write-Host "Configuring publish support." -ForegroundColor Green
        NuGet-Install 'MSBuild.Microsoft.VisualStudio.Web.targets' $PublisherVersion
        $script:tools = "/p`:VSToolsPath=$PublisherPath"

        if($(Get-Mode) -eq 'publish') {
            $pre = '/p:DeployOnBuild=true`;PublishProfile='
            $script:publish = "$pre$PublishProfile"
            Write-Host $publish -ForegroundColor Yellow
        }
        Write-Host $tools -ForegroundColor Yellow
        Write-Host "Publish support configured." -ForegroundColor Green
    } | Out-Null
}