Continuous deployment to Azure Cloud Services
by Frans Lytzen | 12/11/2015We are using this script to deploy manually as well as deploying automatically from our TeamCity server.
Pre-requisites
- Before you start you need to create a Management Certificate for your Azure subscription, import it on your machine and add the thumb print to the script.
- You will also need to install the Azure Powershell CmdLets on your machine (it’s usually installed as part of the SDK). When you look at the script, there are a couple of values you will need to provide.
- Put the script and the configuration XML file in a folder in your solution called Deployment.
- Assuming you want to enable remote access to your servers, you need to create a certificate and put it in the Deployment folder next to the script. You can do this by using Visual Studio to do a deployment first and enable remote access; This will configure the config files with the thumbprint and will generate a certificate for you. If you don’t want remote access, just remove that bit of the script.
Param(
[string]$deploymentType,
[string]$deployToAzure,
[string]$slot
)
$managementCertificateThumbprint="[YOURMANAGEMENTCERTIFICATETHUMBPRINT]"
function Build()
{
$dotNetVersion = "4.0"
$regKey = "HKLM:\software\Microsoft\MSBuild\ToolsVersions\$dotNetVersion"
$regProperty = "MSBuildToolsPath"
$msbuildExe = join-path -path (Get-ItemProperty $regKey).$regProperty -childpath "msbuild.exe"
Write-Host "Building..." -ForegroundColor Yellow
&$msbuildExe [YOURSOLUTIONNAME].sln /t:Publish /p:TargetProfile=$deploymentType /p:Configuration=$deploymentType /p:Platform="Any CPU"
if ($LASTEXITCODE -ne 0)
{
throw "The build failed"
}
}
function Deploy()
{
Import-Module Azure
write-host "`nAzure CmdLets loaded" -ForegroundColor Green
$configFile = $configFolder + "\AzureSettings.xml"
if (! (Test-Path $configFile))
{
Throw ("Unable to find " + $configFile)
}
[xml]$azureSettings = Get-Content ($configFile)
$subscriptionId = $azureSettings.AzureSettings.SubscriptionID
$friendlySubName = $azureSettings.AzureSettings.FriendlySubName
$serviceSettings = ($azureSettings.AzureSettings.Services.Service | Where-Object {$_.deploymentType -match $deploymentType })
$serviceName = $serviceSettings.ServiceName
$storageAccount = $serviceSettings.DeploymentStorageAccount
if (Test-Path cert:\CurrentUser\My\$managementCertificateThumbprint)
{
$myCert = Get-Item cert:\CurrentUser\My\$managementCertificateThumbprint
}
else {
if (Test-Path cert:\LocalMachine\My\$managementCertificateThumbprint) {
$myCert = Get-Item cert:\LocalMachine\My\$managementCertificateThumbprint
}
else {
Throw ("Unable to find the management certificate in either current user or local machine storage")
}
}
Write-Host ("Adding " + $friendlySubName + " subscription") -ForegroundColor Green
Set-AzureSubscription -SubscriptionName $friendlySubName -Certificate $myCert -SubscriptionID $subscriptionId
Select-AzureSubscription $friendlySubName
$deployment = Get-AzureDeployment -ServiceName $serviceName -Slot $slot -ErrorVariable a -ErrorAction silentlycontinue
$deploymentLabel = $deploymentType + " " + $(Get-Date –f $timeStampFormat)
Set-AzureSubscription -SubscriptionName $friendlySubName -CurrentStorageAccount $storageAccount
#Make sure we have the remote access certificate in there
Add-AzureCertificate -ServiceName $serviceName -CertToDeploy ($configFolder + "\RemoteAccessCertificate.pfx") -Password powershellSucks
Write-Host ("About to deploy " + $deploymentType + " " + $service + " to " + $slot + " in " + $location) -ForegroundColor Green
Write-Host ("Subscription: " + $friendlySubName + ", Service: " + $serviceName + ", Storage: " + $storageAccount) -ForegroundColor Green
if ($a[0] -ne $null)
{
#Write-Host "No current deployment, creating a new deployment"
CreateNewDeployment
}
else
{
Write-Host "Existing deployment, upgrading..."
UpgradeDeployment
}
}
function CreateNewDeployment()
{
write-progress -id 3 -activity "Creating New Deployment" -Status "In progress"
Write-Output "$(Get-Date -f $timeStampFormat) - Creating New Deployment: In progress"
$opstat = New-AzureDeployment -Slot $slot -Package $packageLocation -Configuration $cloudConfigLocation -label $deploymentLabel -ServiceName $serviceName
$completeDeployment = Get-AzureDeployment -ServiceName $serviceName -Slot $slot
$completeDeploymentID = $completeDeployment.deploymentid
write-progress -id 3 -activity "Creating New Deployment" -completed -Status "Complete"
Write-Output "$(Get-Date -f $timeStampFormat) - Creating New Deployment: Complete, Deployment ID: $completeDeploymentID"
StartInstances
}
function UpgradeDeployment()
{
write-progress -id 3 -activity "Upgrading Deployment" -Status "In progress"
Write-Output "$(Get-Date -f $timeStampFormat) - Upgrading Deployment: In progress"
# perform Update-Deployment
$setdeployment = Set-AzureDeployment -Upgrade -Slot $slot -Package $packageLocation -Configuration $cloudConfigLocation -label $deploymentLabel -ServiceName $serviceName -Force
$completeDeployment = Get-AzureDeployment -ServiceName $serviceName -Slot $slot
$completeDeploymentID = $completeDeployment.deploymentid
write-progress -id 3 -activity "Upgrading Deployment" -completed -Status "Complete"
Write-Output "$(Get-Date -f $timeStampFormat) - Upgrading Deployment: Complete, Deployment ID: $completeDeploymentID"
}
function StartInstances()
{
write-progress -id 4 -activity "Starting Instances" -status "In progress"
Write-Output "$(Get-Date -f $timeStampFormat) - Starting Instances: In progress"
$deployment = Get-AzureDeployment -ServiceName $serviceName -Slot $slot
$runstatus = $deployment.Status
if ($runstatus -ne 'Running')
{
$run = Set-AzureDeployment -Slot $slot -ServiceName $serviceName -Status Running
}
$deployment = Get-AzureDeployment -ServiceName $serviceName -Slot $slot
$oldStatusStr = @("") * $deployment.RoleInstanceList.Count
while (-not(AllInstancesRunning($deployment.RoleInstanceList)))
{
$i = 1
foreach ($roleInstance in $deployment.RoleInstanceList)
{
$instanceName = $roleInstance.InstanceName
$instanceStatus = $roleInstance.InstanceStatus
if ($oldStatusStr[$i - 1] -ne $roleInstance.InstanceStatus)
{
$oldStatusStr[$i - 1] = $roleInstance.InstanceStatus
Write-Output "$(Get-Date -f $timeStampFormat) - Starting Instance '$instanceName': $instanceStatus"
}
write-progress -id (4 + $i) -activity "Starting Instance '$instanceName'" -status "$instanceStatus"
$i = $i + 1
}
sleep -Seconds 1
$deployment = Get-AzureDeployment -ServiceName $serviceName -Slot $slot
}
$i = 1
foreach ($roleInstance in $deployment.RoleInstanceList)
{
$instanceName = $roleInstance.InstanceName
$instanceStatus = $roleInstance.InstanceStatus
if ($oldStatusStr[$i - 1] -ne $roleInstance.InstanceStatus)
{
$oldStatusStr[$i - 1] = $roleInstance.InstanceStatus
Write-Output "$(Get-Date -f $timeStampFormat) - Starting Instance '$instanceName': $instanceStatus"
}
$i = $i + 1
}
$deployment = Get-AzureDeployment -ServiceName $serviceName -Slot $slot
$opstat = $deployment.Status
write-progress -id 4 -activity "Starting Instances" -completed -status $opstat
Write-Output "$(Get-Date -f $timeStampFormat) - Starting Instances: $opstat"
}
function AllInstancesRunning($roleInstanceList)
{
foreach ($roleInstance in $roleInstanceList)
{
if ($roleInstance.InstanceStatus -ne "ReadyRole")
{
return $false
}
}
return $true
}
#Get to the right place...
$scriptPath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptPath
cd $dir
cd..
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
if (!$isAdmin)
{
throw "You need to run this script as an admin"
}
$configFolder = "Deployment"
if (!(Test-Path $configFolder))
{
Throw ("There is no '" + $configFolder + "' folder. Aborting.")
}
while ((!$deploymentType) -OR ($deploymentType -notin ("Live", "UAT", "Dev" )))
{
$deploymentType = Read-Host "Please specify deployment type Live or UAT (or Dev)"
}
while ((!$slot) -or ($slot -notin ("Production", "Staging")))
{
$slot = Read-Host "Deploy to Production or Staging?"
}
Build
while ((!$deployToAzure ) -or ($deployToAzure -notin ("Y", "N")))
{
$deployToAzure = Read-Host "Do you want to actually deploy this to Azure now (Y/N)?"
}
if ($deployToAzure -eq "Y")
{
$packageLocation = $dir + "\..\Cloud\bin\" + $deploymentType + "\app.publish\Cloud.cspkg"
$cloudConfigLocation = $dir + "\..\Cloud\bin\" + $deploymentType + "\app.publish\ServiceConfiguration." + $deploymentType + ".cscfg"
Deploy
}
Note that some of the above scripts have been found elsewhere on the internet, in particular the functions to actuall create/upgrade deployments.
You will also need a configuration file to hold some information about each of your environments:
<azuresettings>
<subscriptionid>[SUBSCRIPTIONID]</subscriptionid>
<friendlysubname>[WHATEVERYOUWANT]</friendlysubname>
<services>
<service deploymenttype="UAT">
<servicename>[CLOUDSERVICENAME eg myuatcloudservice]</servicename>
<deploymentstorageaccount>[STORAGEACCOUNTNAME eg myuatstorage]</deploymentstorageaccount>
</service>
</services>
<services>
<service deploymenttype="Live">
<servicename>[CLOUDSERVICENAME eg myuatcloudservice]</servicename>
<deploymentstorageaccount>[STORAGEACCOUNTNAME eg myuatstorage]</deploymentstorageaccount>
</service>
</services>
</azuresettings>