Automated releases

Using a simple Powershell script and Amazon AWS, I am able to release changes to RoosterBot with a click of a button. The whole process takes only half a minute and the bot notifies me when it completes.

Building this arrangement has taught me how to do this for any other product, not just C# bots running on a single cloud server. I could adapt this setup to server applications running on multiple VPS instances, or websites, or just about anything you can think of that requires automated deployment.

The gist

The Powershell script creates a build of the solution and uploads the files as a single bundle to Amazon S3. From here, AWS detects that new files are available, and immediately begins the deploy process, which pushes the bundle to the server running the bot. Then, within seconds, the bot is stopped and the new version launched. The bot is offline for only one second, and is immediately ready for operation.

The code

This is a slightly pared-down version of the PS script:

# Read version info
[string[]]$arrayFromFile = Get-Content -Path ".\Release.dat"

# Release.dat contains two lines:
#   the date of the last release, in yyyy-mm-dd
#   the number of releases on that date
$releaseDateString = $arrayFromFile[0]
$currentDateString = Get-Date -UFormat "%Y-%m-%d"
$releaseNumber = 1;
if ($releaseDateString -eq $currentDateString) {
	# If today is not the same date as the last release, leave the number at one.
	# Otherwise, add one to the number.
	$releaseNumber = ([int] $arrayFromFile[1]) + 1;
}

# Write the current date and the number back to Release.dat
@"
$currentDateString
$releaseNumber
"@ | Out-File -FilePath ".\Release.dat"

# Regenerate Constants.cs because it contains the release number for the C# code
& TextTransform.exe

# Build
& MSBuild.exe .\RoosterBot.sln

# Copy program into ./Release
Copy-Item ".\RoosterBot\bin\Release\*" -Destination ".\Release\RoosterBot" -Recurse

# Create a zip file with the contents of ./Release
Compress-Archive -Path Release\* -DestinationPath Release\release.zip

# Upload the zip to S3. This will initiate the pipeline and quickly cause deployment.
Write-S3Object Release\release.zip

It reads the “release.dat” file which has the date of the last release on the first line, and the number of releases that have been done on that day. The script reads this file, and if the last release was today, it increments the number. It then regenerates the T4 code templates, of which there is currently only Constants.tt, which contains this:

public static readonly DateTime CurrentReleaseDate = new DateTime(<#= DateTime.Today.Year #>, <#= DateTime.Today.Month #>, <#= DateTime.Today.Day #>);
public static readonly int ReleasesToday = <#= File.ReadAllLines(Host.ResolvePath("../Release.dat"))[1] #>;

public static readonly DateTime FirstReleaseDate = new DateTime(2018, 10, 1);
public static readonly int DaysSinceV1 = (int) (CurrentReleaseDate - FirstReleaseDate).TotalDays;
public static readonly string VersionString = DaysSinceV1.ToString() + "." + ReleasesToday;

When this file is regenerated, it contains a version string that looks like “175.3”, where 175 means that it has been 175 days since the first release, and this is the 3rd release on that day. When this is deployed, the version string will not change until the next release.

After the script uploads the bundle to a specific S3 bucket, an AWS CodePipeline is notified via a CloudWatch event that files have changed. The pipeline does not have to build anything (it used to be that the project was built in the cloud, but this cost me money, and was very slow), so it starts the deploy process immediately.

The deploy process involves only a single server, so it is quite fast. The server detects that there is a new version, downloads it, stops the bot using an additional executable included in the bundle (explained below), and then starts the new version. It waits for 10 seconds, and if the bot has not shut down by then, the release is considered successful.

The bot is shut down using an “AppStop.exe” packed with the bot. The bot itself has a named pipe which is polled for “stop” messages. The AppStop.exe sends this message, so executing this program will cause the bot to stop.