Roles

When performing deployment activities with powerdelivery, the real work that applies changes to your nodes happens in roles. These are PowerShell scripts within the Roles directory of your project in their own directory named after the role.

Generating roles

To assist with creating roles, you can use the New-DeliveryRole cmdlet to generate one for your project.


Below is an example of creating a role for installing the chocolatey package manager:

Administrator: Windows PowerShell
PS C:\MyApp\MyAppDelivery> New-DeliveryRole Chocolatey
Role created at ".\Roles\Chocolatey"


After this cmdlet completes it will have added the following to your directory structure:

C:\MyApp\MyAppDelivery\
    |-- Roles\
        |-- Chocolatey\
            |-- Always.ps1
            |-- Migrations\


The contents of Always.ps1 as generated looks like this:

Delivery:Role -Up {
  param($target, $config, $node)

  # Write PowerShell deployment logic here

} -Down {
  param($target, $config, $node)

  # Write PowerShell rollback logic here (if necessary)

}

Anatomy of a role script

Every role script must contain the Delivery:Role statement. After this statement, an -Up block specifies what will happen when a normal deployment. If specified, a -Down block will run when you request a rollback. The down block is optional, and if you specify just one block without a name, it will be used as the up block.


The example below shows a script with only the default (up) block:

Delivery:Role {
  param($target, $config, $node)

  # Write PowerShell deployment logic here. 
  # This role doesn't support rollback.
}


Role script parameters

Every block in a role, whether performing an up or down (rollback), must declare three parameters. These are the $target, $config, and $node parameters. These parameters provide context to where your role is running and will be very useful depending on what you’re trying to do.


Types of role scripts

There are two types of role scripts. Read the sections below to decide which types make the most sense for your particular infrastructure and release process.


The “Always” role script

Always.ps1 runs, as so aptly named, every time the role is applied. If you are provisioning nodes at deploy time you can put all of your role logic here, as it will be applied to a fresh node every time you deploy; regardless of the target environment.


Role migration scripts

If you are deploying to nodes that were provisioned ahead of time and are not to be re-created from scratch on each deployment, consider using role migration scripts. Role migration scripts are similar to data migrations in ruby on rails. They allow you to script a set of changes that will be made to a node and associated with a version, so the node upon which they are run remembers whether it was already applied.

Migration scripts are important because without them, if someone deploys changes to one environment (say, a development server) and then changes the script, the test environment may never get that change applied to it once it is deployed to. With migration scripts, changes are rolled forward in a predictable fashion.


Generating a role migration script

Use the New-DeliveryRoleMigration cmdlet to add a migration to an existing role in your project. The role you add the migration to must already have been created by the New-DeliveryRole cmdlet.


Below is an example of adding a migration script to a role named Database:

Administrator: Windows PowerShell
PS C:\MyApp\MyAppDelivery> New-DeliveryRoleMigration MyApp Database "Truncate Logfile"
Role migration created at ".\MyAppDelivery\Roles\Database\Migrations\20151019_112811_Truncate_Logfile.ps1"


Tips:

  • The content of a role migration script is just like Always.ps1. You can specify just the up block, or both up and down.
  • Always.ps1 runs before any migration scripts if present during a normal deployment.
  • Always.ps1 runs after any migration scripts if present when rolling back.


Common role tasks

Below are some examples of common tasks you might want to accomplish in roles.


Installing dependencies

If a role is set to run on localhost, it may use any of the PowerShell cmdlets installed on a developer or build server computer. However, if a role runs on a remote computer, any PowerShell cmdlets you want to use need to be installed before you can use them. Many PowerShell cmdlets, including powerdelivery itself, are installed with the popular chocolatey package manager.

Lets modify our script we generated at the beginning of this topic by editing the file Always.ps1 to install chocolatey:


Delivery:Role {
  param($target, $config, $node)

  if (!(Test-CommandExists choco)) {
    $webClient = New-Object Net.WebClient
    Invoke-Expression $webClient.DownloadString('https://chocolatey.org/install.ps1')
    $path = $env:PATH
    $allUsersProfile = $env:ALLUSERSPROFILE
    $env:PATH = "$path;$allUsersProfile\chocolatey\bin"
  }
}
MyAppDelivery\Roles\Chocolatey\Always.ps1


This role uses the Test-CommandExists cmdlet to only execute if chocolatey is not already available. Now if we want any remote node to have chocolatey installed, we can just apply the “Chocolatey” role to any nodes we want in a target script. We can also add calls to the choco command-line client to install packages in this role, or another that is set to run after it in our target.


Tip: You may use the Web Platform Installer, OneGet, npm, rubygems, or whatever you need to install dependencies - chocolatey is just one example.


Publishing files to a shared location

A commonly needed task is to publish files to remote nodes for a release. This is two step process, where first you must publish the files from the computer running powerdelivery to a shared location, and then the remote nodes download them.

The example role below creates a directory named after the powerdelivery project on a shared drive somewhere on your network. It then creates a release directory with the timestamp of the current release where that you can copy to. Depending on your infrastructure, you may wish to publish files instead to a Windows Azure storage container, an Amazon Web Services S3 bucket, or DropBox. This role should run on localhost.

The script below demonstrates publishing files:


Delivery:Role {
  param($target, $config, $node)

  # An example share path.
  $sharePath = "\\COMPUTERNAME\share"

  # Reference a sub-directory named after the project
  $projectPath = Join-Path $sharePath $target.ProjectName

  # Create a directory for this release
  $thisReleasePath = Join-Path $projectPath $target.StartedAt
  New-Item $thisReleasePath -ItemType Directory | Out-Null

  # TODO: Copy files from the local computer 
  # into $thisReleasePath here!
}
MyAppDelivery\Roles\Publish\Always.ps1


Managing releases on nodes

Once files have been released to a shared location as described above, nodes that need any of the files should create a directory into which to copy or download them. To support rollback, it is necessary to retain the previous release. This role needs to run on any node that files are being downloaded to.

The PowerDeliveryNode PowerShell module, installable from chocolatey on your remote nodes, includes two powerful cmdlets that help you with this. They will create release directories with a symbolic link pointing to the latest one, and allow you to rollback. You just copy your files into the directories they manage.

The script below demonstrates creating and rolling back releases:


Delivery:Role -Up {
  param($target, $config, $node)

  Import-Module PowerDeliveryNode

  # Get the path to <Drive>:\Users\<User>\AppData\Roaming
  $appData = [Environment]::GetFolderPath("ApplicationData")
  $releasePath = New-DeliveryReleasePath $target $appData

  # TODO: Make any changes needed on the node to support 
  # the new release.

} -Down {
  param($target, $config, $node)

  Import-Module PowerDeliveryNode

  # Get the path to <Drive>:\Users\<User>\AppData\Roaming
  $appData = [Environment]::GetFolderPath("ApplicationData")
  $releasePath = Undo-DeliveryReleasePath $target $appData

  # TODO: Make any changes needed on the node to reflect that 
  # "Current" now points to the previous release.
}
MyAppDelivery\Roles\Release\Always.ps1


Next read about targets.