Skip to content

Instantly share code, notes, and snippets.

@mvalipour
Last active July 27, 2016 21:57
Show Gist options
  • Select an option

  • Save mvalipour/12622c70a26853f591704015c169f28a to your computer and use it in GitHub Desktop.

Select an option

Save mvalipour/12622c70a26853f591704015c169f28a to your computer and use it in GitHub Desktop.
Azure Cloud Service Octopus Continuous Delivery Setup

Step 1. Deploy to Azure

  • Pre-Deploy
    • Log octopus parameters -- A
    • Transform package: Run service definition, service configuration, web/app config and file transformations/replacement bundled into the package. -- B
  • Post-Deploy
    • Log deployment -- C
    • Ensure/Add diagnostics extensions -- D
    • Wait loop for /healthcheck endpoint -- E
    • Notify Slack/Skype -- F
  • Deploy-failed
    • Notify Slack/Skype -- F

Step 2. Manual Intervention: Happy with Staging? -- live only

Step 3. Swap VIPs -- G

  • Log deployment labels
  • Sanity check staging deployment label against version number
  • Wait loop for running status on staging deployment
  • Swap VIPs

Step 4. Manual Intervention: Happy with Production? -- live only

Step 5. Delete Staging -- H

  • Sanity check production label against version number
  • Wait loop for running status on production deployment.
  • Delete staging deployment

A

Write-Host ($OctopusParameters | Out-String)

B

# This is an extensive list of conventions to allow multi-tenant deployment of packages 
# to apply transformations bundled in the nuget package to the following files
#
# App.Config in worker roles
# Web.Config in web roles
# ServiceDefinition.csdef
# ServiceConfiguration.cscfg
# and file replacement throughout the web directory

C

function Get-Deployment()
{
    Select-AzureSubscription -SubscriptionId $azureSubscriptionId
    $deployment = Get-AzureDeployment -Slot $azureSlot -ServiceName $azureServiceName

    $global:websiteRoot = $deployment | Select -ExpandProperty Url | Select -ExpandProperty OriginalString
    Write-Host "Website Root: $websiteRoot"

    return $deployment;
}

# main
Get-Deployment | ft -AutoSize | Out-Host

D

Set-AzureSubscription -SubscriptionId $azureSubscriptionId -CurrentStorageAccount $OctopusAzureStorageAccountName

$existing = Get-AzureServiceDiagnosticsExtension -ServiceName $OctopusAzureServiceName -Slot $OctopusAzureSlot
if($existing) 
{
    Write-Host "Diagnostics extensions already configured."
}
else 
{
    Write-Host "Adding Diagnostics extensions..."

	$storageCtx = New-AzureStorageContext -StorageAccountName $diagnosticsStorageAccount -StorageAccountKey $diagnosticsStorageAccountKey
	Remove-AzureServiceDiagnosticsExtension -ServiceName $OctopusAzureServiceName -Slot $OctopusAzureSlot
	Set-AzureServiceDiagnosticsExtension -ServiceName $OctopusAzureServiceName -Slot $OctopusAzureSlot -StorageContext $storageCtx -DiagnosticsConfigurationPath (Join-Path $root "diagnostics.wadcfgx")
    
    Write-Host "Diagnostics extensions added."
}

E

$endpoint = "$websiteRoot/healthcheck"
$startTime = [DateTime]::Now
$lapsed = 0

Write-Host "Healthcheck wait loop started. end-point: $endpoint"

while($true) {
    try {
        $res = Invoke-WebRequest -Uri $endpoint -TimeoutSec 30
        $data = $res.Content | ConvertFrom-Json

        if($data.Status -eq 0 ) { break }
    }
    catch [System.Net.WebException] { }

    $lapsed = ([DateTime]::Now - $startTime).TotalMinutes
    if($lapsed > 15) {
        Write-Error "Healthcheck wait loop time-out after $lapsed mins"
        return
    }

    Start-Sleep -s 1
}

Write-Host "Healthcheck completed after $lapsed mins"

F

function Slack-Rich-Notification ($notification)
{
    $payload = @{
        channel = $NotificationSlackChannel
        username = $NotificationSlackUsername
        icon_url = $NotificationSlackIconUrl
        attachments = @(@{
            fallback = $notification.fallback
            color = $notification.color
            fields = @(@{
                title = $notification.title
                value = $notification.value
            })
            mrkdwn_in = @("fields")
        });
    }

    Invoke-RestMethod -Method POST -Body ($payload | ConvertTo-Json -Depth 4) -Uri $NotificationSlackHookUrl  -ContentType 'application/json'
}

if ($OctopusParameters['Octopus.Deployment.Error'] -eq $null){
    Slack-Rich-Notification @{
        title = "Success";
        value = "Deployed *$OctopusProjectName* release ``$OctopusReleaseNumber`` to $OctopusEnvironmentName | <$websiteRoot/app | Browse>";
        fallback = "Deployed $OctopusProjectName release $OctopusReleaseNumber to $OctopusEnvironmentName successfully";
        color = "good";
    };
} else {
    Slack-Rich-Notification @{
        title = "Failed";
        value = "Failed to deploy $OctopusProjectName release $OctopusReleaseNumber to $OctopusEnvironmentName";
        fallback = "Failed to deploy $OctopusProjectName release $OctopusReleaseNumber to $OctopusEnvironmentName";
        color = "danger";
    };
}

G

$serviceName = $OctopusParameters["AzureSettings.ServiceName"] 

$productionDeployment = Get-AzureDeployment -ServiceName $serviceName -Slot "Production"
$stagingDeployment = Get-AzureDeployment -ServiceName $serviceName -Slot "Staging"
    
$productionLabel = $productionDeployment.Label
$stagingLabel = $stagingDeployment.Label

Write-Host "Production deployment: $productionLabel, created $productionTime"
Write-Host "Staging deployment: $stagingLabel, created $stagingTime"

if(-not($stagingLabel -match "v$OctopusReleaseNumber"))
{
    Write-Error "Staging label does not match version: v$OctopusReleaseNumber"
    return
}

# Wait until it's all up    
while (-not $($stagingDeployment.Status -eq "Running"))
{
    Write-Host "Waiting 10 seconds for staging running status..."
	Start-Sleep -s 10
    $stagingDeployment = Get-AzureDeployment -ServiceName $serviceName -Slot "Staging"
}

Write-Host "Swap Started" -ForegroundColor Green
$moveStatus = Move-AzureDeployment -ServiceName $serviceName
Write-Output ("Vip swap of {0} status: {1}" -f $serviceName, $moveStatus.OperationStatus)

H

$serviceName = $OctopusParameters["AzureSettings.ServiceName"] 

$productionDeployment = Get-AzureDeployment -ServiceName $serviceName -Slot "Production"
$productionLabel = $productionDeployment.Label

if(-not($productionLabel -match "v$OctopusReleaseNumber"))
{
    Write-Error "Production label does not match version: v$OctopusReleaseNumber"
    return
}

# Wait until it's all up    
while (-not $($productionDeployment.Status -eq "Running"))
{
    Write-Host "Waiting 10 seconds for production running status..."
	Start-Sleep -s 10
    $productionDeployment = Get-AzureDeployment -ServiceName $serviceName -Slot "Production"
}

Write-Host "Delete Staging started" -ForegroundColor Green
$removeStatus = Remove-AzureDeployment -ServiceName $serviceName -Slot "Staging" -Force 
Write-Output ("Delete staging of {0} status: {1}" -f $serviceName, $removeStatus.OperationStatus)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment