The Unfold deployment tasks

In the previous post I explained how to install and use Unfold, the PowerShell deployment solution for .net web applications. Several times I mentioned the default unfold tasks. That's right, Unfold comes with a set of Psake tasks that are the framework for a typical deployment flow. In this blog post, we'll explain the different tasks and how they cooperate to perform the deployment.

Let's start by listing all of the tasks that are available for your deployment. Simply

.\unfold.ps1 -docs

This will print out a list of all tasks that you can invoke from the command-line. The deploy task is the most important one. It performs all necessary operations to fully deploy your application. This is an explanation of the subtasks that is it invokes. These tasks are simply defined using psake.

So if you execute

.\unfold.ps1 deploy -to staging

Then these are the tasks that get executed.

  • setup:

The setup tasks is pretty simple. It ensures that the $config.basePath value is available on the deployment target. Once it's there we can start all kinds of other operations like updating the source code or composing a "release".

It's also important to note that after this task has executed, Invoke-Script will automatically change the current location to the $config.basePath value when executing a script block. As a result all operations within an Invoke-Script block can use relative paths.

  • updatecode:

The purpose of updatecode is pretty obvious. It ensures that the $config.basePath folder contains a subfolder called code that contains the latest version of your code. This code if fetched from source control, not a local copy. If the folder is already there, we perform an update to the latest version. If not, then we simply perform an initial checkout.

You can configure where to get the code from using two configuration calls:

Set-Config scm git
Set-Config repository "git@github.com:your-account/your-repository.git"

Currently we support two scm's: git and svn. We take pull requests for others.

It's important to note that by default this fetch from source control happens on the deployment machine. So you have to make sure that your deployment machine can access source control (ssh keys, firewall restrictions, etc). This also means your code will also be built on the remote machine. If you don't want code on the remote machine, you can force unfold to perform all tasks up to release to be executed on the local machine. To do so, add this to your deploy recipe.

Set-Config localbuild $true
Set-Config localbuildpath "c:\path\to\perform\local\build"
  • build:

Unlike Ruby or PHP projects .net projects need to be built using msbuild, there's no way around it. By default, when there's no specific configuration, we look for a Web Application project inside the code repository. If we find one, that's what we build. However it is also possible to specify which projects or solution files that we need to build. To do so, simply specify a configuration variable containing a list of msbuild files to execute. Additionally, you can also specify the build configuration we need to use, but it's not required.

Set-Config msbuild @('.\code\path\to\your\Project.csproj')
Set-Config buildConfiguration "Release"

That's all we need to know.

  • release:

Once your code is built successfully, we can create what we call a release. A release is folder that contains the build result for a given revision. It should be an application that can run successfully. We put those under the $config.basePath and the folder name for a typical release looks like this:

20120905_1218_a6b9e4c_YourProject

It is composed of 1. the date at which the release was created 1. the hour of creation 1. a scm revision reference, for git (as shown here) this is the revision hash 1. the name of your project

This offers you a way to immediately see when a release was created, and from which point in your revision history. This can be of great help when problems are occuring in your application and you want to know from which revision the code was built.

If your build msbuild project is a Web Application Project, then we copy the build output to a folder below this called web. If not, you'll have to implement a release operation yourself. We'll explain this in a future blog post.

We copy it under a web folder, because it will allow you to also release other build artefacts under different folders. Maybe you have a set of Windows Services, or maybe you should also install a dedicated web service. If so, then these can be released next to the web output.

Once this task is completed, the name of the release is available on the $config object as $config.releasepath.

After you have executed multiple deployments, the $config.baseFolder will look like this.

layout

What you see are: 1. Five releases, each contains a web subfolder, and possibly others 2. a code folder, which contains the current scm checkout 3. a current link, established by the finalize task

We keep a couple of releases around, because this allows us to very quickly rollback in case something goes wrong. When that happens, we can simply re-point IIS to one of the previous releases, and we're all fine again.

  • releasefinalize:

this is an unfold internal task, that will finalize the work that is done in the previous step.

  • setupapppool:

As the name explains, this will create an IIS Application Pool for your application. We consider it best practice to give each application its own applcation pool. This task ensures that the application pool is setup. You can specify the name of the applicatin pool using Set-Config apppool "yourapppool", if that's not set, we use the iisname (which is the name of the website under IIS). If that's not set either, we use the project configuration variable.

All operations on IIS operations are performed using the WebAdministration Module

  • uninstallcurrentrelease:

If the web folder of a release contains a file called App_Offline.html, we rename it to App_Offline.htm. This will disable your web application until the new release is fully configured. If there is other stuff that needs to be done - like stopping Windows Services, killing an exe - than this is the place where you will hook your custom operations onto. More on this in a future post.

  • setupiis:

We're almost there. This task configures the IIS website for you, it uses the application pool we created a couple of steps ago, and creates or updates a website to point to the release we created. This task uses two configuration variables:

Set-Config iisname "www.yoursite.com"
Set-Config bindings @(
                      @{protocol="http";bindingInformation="*:80:www.yoursite.com"},
                      @{protocol="http";bindingInformation="*:80:yoursite.com"}
                     )

This IIS Name, is the name that's used inside IIS, the bindings configuration defines the IIS bindings for your website. For more information on defining bindings, simply check the WebAdministration documentation.

  • finalize:

This task wraps it all up. It creates a link called current pointing to the latest release. That way you can quickly navigate to the currently deployed version, and it allows us to detect which version is the current one. It moves the App_Offline.htm out of the way again, so the newly created release becomes active.

Finally, it purges old releases. There is the $config.keep option, which by default is set to 5, and it defines the number of releases you want to keep. If you want 10, simply add this to your deploy recipe

Set-Config keep 10

Conclusion

You've now seen which steps Unfold performs in order to fully deploy your application. Most of them have options which you can simply configure using Set-Config calls.

It's important to note that this flow is completely extensible. We'll dedicate a future blog post to how you can create custom tasks and hook them to the default flow or how you can override them. By doing so, you can perfectly tailor deployments for your own needs.

Introducing Unfold

Unfold is a capistrano-like deployment solution for .net applications. I created it because it didn't find any solution that gave me enough flexibility and at the same time provided enough functionality out-of-the-box to deploy standard applications. In Unix environments, when it comes to deploying web applications, there's nothing that rivals Capistrano. So what I wanted was pretty simple: a capistrano for .net/windows. This led me to the following design decisions:

  • Powershell:

we use only Powershell. In terms of automating things, there's nothing more powerful than Powershell in the Windows world. You can execute any application, there's a wealth of Modules that allow you to automate anything from installing a Windows Service, configuring IIS or creating folders

  • Psake:

Capistrano is built on top of Rake and it's a great way of splitting a deployment process into smaller components. Makes your flow easy to understand. I wanted that too, so I simply used Psake.

  • Powershell Remoting:

for access to remote machines, we depend on Powershell Remoting. It makes it very easy to execute scripts or ScriptBlocks on remote machines. For an easy explanation of Powershell Remoting, check here. The unfold wiki contains an article on how to setup Powershell remoting for your own target server.

  • Good defaults with good extensibilty:

Unfold had to contain a default deployment flow that is good enough for simple applications, if you need customization, then you should be able to built upon that flow.

The source code is on GitHub.

1. Installation

To install unfold, open a Powershell command-prompt and issue:

cd ~\Documents\WindowsPowerShell\Modules
git clone https://github.com/thomasvm/unfold.git 

To check whether the installation was successful, import the module.

Import-Module unfold    

Great!

Add unfold to your project

Now navigate towards a project folder where you want to specify your deployment recipe. For example, if your source code sits inside a src folder, then the parent folder is ideal. Once you're there do:

Install-Unfold 

This will create a folder called deployment, containing two files

  • deploy.ps1:

this file is the most important file, as it contains all your configuration and deployment steps. It's the file that you customize to define your deployment.

  • unfold.ps1:

this file is for loading the Unfold module and passing the command-line parameters in to it. More on that later

2. The deploy.ps1 file

Let's have a look at a slightly modified deploy.ps1 file:

# Configuration
Set-Config project "YourProjectName"

Set-Config scm git
Set-Config repository "git@github.com:yourusername/a-project.git"

# Environment to use when not specified
Set-Config default dev

# Specify a custom set of build files, don't forget
# to prepend with ".\code\" because that is the remote checkout folder
Set-Config msbuild = @('.\code\src\YourWebProject\YourWebProject.csproj')

Set-Environment dev {
    # machine to deploy to
    Set-Config machine "localhost"
    Set-Config basePath "c:\inetpub\wwwroot\dev\YourProject" 
}

Set-Environment staging {
    Set-Config machine "your.machine.name"
    Set-Config basePath "d:\sites\YourProjectName\staging"
}

# Import the default Unfold deployment flow
Import-DefaultTasks

# Custom task
task ping {
    Invoke-Script {
        Write-Host $env:ComputerName
        Write-Host $(pwd)
        Write-Host "On this machine we'll deploy to $($config.basePath)"
    }
}
# Set deploy as default task
task Default -depends "deploy"

Yep, that's all there is to it, this is a full deployment script for a standard asp.net application. To test whether this works, open a PowerShell inside the folder and issue:

.\unfold.ps1 deploy -to dev

In other words: call the launcher script unfold.ps1 and let it invoke the deploy task (that is imported through Import-DefaultTasks) and deploy towards the dev environment.

Of course, most of the time you'll want to extend this. That's for a later post, for now, we'll have a look at what's inside this file.

2.a The configuration part

The deploy.ps1 file starts off with a set of calls to a function called Set-Config. These calls populate a $config variable that is available throughout your deployment. Most of the variables in the example above are used by the standard Unfold deployment tasks, but you can add your own if you need to. There are no restrictions. You've probably also noticed that some calls are wrapped within a Set-Environment function call. These configuration settings are considered to be environment-specific. In the above example, if you deploy using

.\unfold.ps1 deploy -to dev

Then $config.machine will be localhost, and the $config.basePath will point to c:\inetpub\wwwroot\dev\YourProject. On the other hand, if the -to parameter is set to "staging"

.\unfold.ps1 deploy -to staging

Then this will assign the values will be your.machine.name and d:\sites\YourProjectName\staging.

2.b The tasks part

Now that the configuration part is done, we're ready to define some tasks. If your application doesn't require too much special, it's usually enough to just import the default Unfold tasks. That's exactly what this line of code does

Import-DefaultTasks

It imports a set of Psake tasks that together compose the deployment flow. A future blog post will explain which tasks there are and what their purpose is.

Next, we define a custom task, this can be used from anything like restarting windows services, clearing a cache folder, or checking whether a process is running. The little task that we define here is to check whether we can connect to our deployment machine.

# Custom task
task ping {
    Invoke-Script {
        Write-Host $env:ComputerName
        Write-Host $(pwd)
        Write-Host "On this machine we'll deploy to $($config.basePath)"
    }
}

This task writes out the computername, the current folder, and the basePath property of the $config global variable we composed using our Set-Config calls. What is important however, is that we do this inside a powershell scriptblock that we pass to a function called Invoke-Script.

This Invoke-Script is a very, very important part of Unfold because it allows you to write deployment operations that work on both your local machine as on a remote machine.

2.c Invoke-Script

The Invoke-Script function has 1 important parameter, namely the scriptblock that you pass into it. With that scriptblock, Invoke-Script performs the following operation.

  • It checks the $config.machine parameter, and based on its value it decides where to execute the scriptblock.
  • If the value equals "localhost", then Invoke-Script simply changes the current directory to $config.basePath and executes the provided scriptblock.
  • If however, the value of $config.machine is something else, like an IP address, a computername or a remote hostname, then it creates a new PS-Session through the New-PSSession function and uses that session to execute the scriptblock on the remote machine.

So, Invoke-Script allows you to write very transparent deployment code, but where to execute it, is completely dependant on what the current configuration is.

In the above example, if the deployment is issues like this:

.\unfold.ps1 ping -to dev

Then the ping task will be executed and will write out the computername of the local machine. However, if we ask Unfold to execute the ping task on staging, it will print out the computername of your.machine.name.

.\unfold.ps1 ping -to staging

You may ask the question: "where do you get the credentials from when opening a session on a remote machine?". The answer is very simple: from the Windows Credential Manager. Just open the Windows Credential Manager from your start menu and add a generic principal to it. This means we don't have to store credentials in our deployment scripts. The name for the principal must reflect the name you've configured using:

Set-Config machine "your.machine.name"

The result of all this is that Invoke-Script allows you to write deployment code that is easily portable between environments and it makes setting up a new environment for your deployments just a matter of adding an extra Set-Environment call to your deployment script.

3.Conclusion

You've now seen how you can easily create an Unfold deployment for your project, how the configuration system works and how to define customs deployment tasks.

In future posts, we'll explain what the default deployment tasks are defined by Unfold and how to extend them with your own tasks. By doing so, you'll be able to complete customize your deployment and make it completely automated and runnable from the command-line.