Use Deployment API to Deploy a Hotfix in Optimizely/Episerver DXP

Use Deployment API to Deploy a Hotfix in Optimizely/Episerver DXP

If for some reason you need to deploy a hotfix to any environment in DXP sometimes is annoying to use the paasportal to move from the code to one environment to another. The solution is to use the deployment API provided by Optimizely which allows you to make this kind of actions. You can find more information about the deployment API here. In this blog post we are going to configure an Azure Pipeline to send a built branch to the environment we need. So without further due lets begin.

First, we will need the build pipeline which will generate the package to be send to the deployment API. You can see an example of the steps we need in the following image.

There is also a previous step called Get sources which will allow you to define what is the default branch to build and if is going to be executed automatically when changes appear. Now lets go step by step. Every parameter not mentioned should go with blanks.

StepNameTypeParameters
1Use NuGet 5.8.1NuGet tool installerTask version: 0
Version of NuGet.exe to install: 5.8.1
2NuGet restoreNuGetTask version: 2
Command: restore
Path to solution, packages.config, or project.json: YourSolutionName.sln
Feeds to use: Feeds in my NuGet.config
Path to NuGet.config: Nuget.config (Or your nuget.config file name)
3Build solutionVisual Studio buildTask version: 1
Solution: YourSolutionName.sln
Visual Studio Version: Latest
MSBuild Arguments:
/p:DeployOnBuild=true /p:DeployDefaultTarget=WebPublish /p:WebPublishMethod=FileSystem /p:PublishProvider=FileSystem /p:ExcludeApp_Data=False /p:publishUrl=”$(Agent.TempDirectory)/SitePackageContent/wwwroot” /p:DeleteExistingFiles=False
Platform: $(BuildPlatform)
Configuration: $(BuildConfiguration)
Clean: Checked
4Publish symbols pathIndex sources and publish symbolsTask version: 2
Path to symbols folder: $(Build.SourcesDirectory)
Search pattern: *\bin**.pdb
Index sources: Checked
Verbose logging: Checked
Artifact name: Symbols_$(BuildConfiguration)
5Archive Archive filesTask version: 2
Root folder or file to archive: $(Agent.TempDirectory)/SitePackageContent
Archive type: zip
Archive file to create: $(Build.ArtifactStagingDirectory)/cms.app.$(Build.BuildId).nupkg
Replace existing archive: Checked
6Publish ArtifactPublish build artifactsTask version: 1
Path to publish: $(build.artifactstagingdirectory)
Artifact name: DXC Deployment Package
Artifact publish location: Azure Pipelines

With these steps we now have an artifact as a zip file that we can use to send it to DXP using a release pipeline. The zip file is located in the azure pipelines site but can also be moved to a file share. An example of the steps required for the release pipeline can be seen in the following image

Again lets go step by step. Every parameter not mentioned should go with blanks.

StepNameTypeParameters
1Upload PackagePowerShell
Task version: 2
Type: Inline
Script:
Write-Host “Installing Azure.Storage Powershell Module”
Install-Module -Name Azure.Storage -Scope CurrentUser -Repository PSGallery -Force -AllowClobber

$env:PSModulePath = “C:\Modules\azurerm_6.7.0;” + $env:PSModulePath
$rootPath = “$env:System_DefaultWorkingDirectory_Name_Of_Your_Azure_Directory-ASP.NET-CI\DXC Deployment Package\”

if (-not (Get-Module -Name EpiCloud -ListAvailable)) {
Install-Module EpiCloud -Scope CurrentUser -Force
}

$resolvedPackagePath = Get-ChildItem -Path $rootPath -Filter *.nupkg

$getEpiDeploymentPackageLocationSplat = @{
ClientKey = “$(PreProduction.ClientKey)”
ClientSecret = “$( PreProduction .ClientSecret)”

ProjectId = “$(ProjectId)”
}

$packageLocation = Get-EpiDeploymentPackageLocation @getEpiDeploymentPackageLocationSplat

Add-EpiDeploymentPackage -SasUrl $packageLocation -Path $resolvedPackagePath.FullName
2Deploy Package to Slot PowerShell
Task version: 2
Type: Inline
Script:
$rootPath = “$env:System_DefaultWorkingDirectory_Name_Of_Your_Azure_Directory-ASP.NET-CI\DXC Deployment Package\”

if (-not (Get-Module -Name EpiCloud -ListAvailable)) {
Install-Module EpiCloud -Scope CurrentUser -Force
}

$resolvedPackagePath = Get-ChildItem -Path $rootPath -Filter *.nupkg

$startEpiDeploymentSplat = @{
DeploymentPackage = $resolvedPackagePath.Name
ProjectId = “$(ProjectId)”
Wait = $true
TargetEnvironment = ‘Preproduction’
UseMaintenancePage = $(UseMaintenancePage)
ClientKey = “$(PreProduction.ClientKey)”
ClientSecret = “$( PreProduction .ClientSecret)”

}

$deploy = Start-EpiDeployment @startEpiDeploymentSplat

$deploy

Write-Host “##vso[task.setvariable variable=DeploymentId;]$($deploy.id)”
3Smoke and Reset If Fails PowerShell
Task version: 2
Type: Inline
Script:
Write-Host “Start sleep for $(SleepBeforeStart) seconds before we start check URL(s).”

Start-Sleep $(SleepBeforeStart)

$urlsArray = “$(Urls)” -split ‘,’
Write-Host “Start smoketest $(Urls)”

$numberOfErrors = 0
$numberOfRetries = 0
$retry = $true
while ($(Retries) -ge $numberOfRetries -and $retry -eq $true){
$retry = $false
for ($i = 0; $i -le $urlsArray.Length – 1; $i++) {
$sw = [Diagnostics.StopWatch]::StartNew()
$sw.Start()
$uri = $urlsArray[$i]
Write-Output “Executing request for URI $uri”
try {
#if ([string]::IsNullOrEmpty(“$headers”)) {
$response = Invoke-WebRequest -Uri $uri -UseBasicParsing -Verbose:$false -MaximumRedirection 0
#}
#else {
#$headersHashTable = $headers | ConvertFrom-Json -AsHashtable #Must wait to ps v6. Have not find any other way to convert string to IDictionary
# $response = Invoke-WebRequest -Uri $uri -Headers $headersHashTable -UseBasicParsing -Verbose:$false -MaximumRedirection 0
#}
$sw.Stop()
$statusCode = $response.StatusCode
$seconds = $sw.Elapsed.TotalSeconds
if ($statusCode -eq 200) {
$statusDescription = $response.StatusDescription
Write-Output “##[ok] $uri => Status: $statusCode $statusDescription in $seconds seconds”
}
else {
Write-Output “##[warning] $uri => Error $statusCode after $seconds seconds”
Write-Output “##vso[task.logissue type=warning;] $uri => Error $statusCode after $seconds seconds”
$numberOfErrors = $numberOfErrors + 1
}
}
catch {
$sw.Stop()
$statusCode = $_.Exception.Response.StatusCode.value__
$errorMessage = $_.Exception.Message
$seconds = $sw.Elapsed.TotalSeconds
Write-Output “##vso[task.logissue type=warning;] $uri => Error $statusCode after $seconds seconds: $errorMessage “
$numberOfErrors = $numberOfErrors + 1
}
}

if ($numberOfErrors -gt 0 -and $numberOfRetries -lt $(Retries)) {
Write-Host “We found ERRORS. But we will retry in $(SleepBeforeRetry) seconds.”
$numberOfErrors = 0
Start-Sleep $(SleepBeforeRetry)
$retry = $true
$numberOfRetries++
}
}

if ($numberOfErrors -gt 0) {
Write-Host “We found ERRORS. Smoketest fails. We will set reset flag to TRUE.”
Write-Host “##vso[task.setvariable variable=ResetDeployment;]true”
$resetDeployment = $true
}
else {
Write-Host “We found no errors. Smoketest success. We will set reset flag to false.”
Write-Host “##vso[task.setvariable variable=ResetDeployment;]false”
$resetDeployment = $false
}

if ($resetDeployment -eq $true) {
if (-not (Get-Module -Name EpiCloud -ListAvailable)) {
Install-Module EpiCloud -Scope CurrentUser -Force
}

Connect-EpiCloud -ClientKey $(Integration.ClientKey) -ClientSecret $(Integration.ClientSecret)

$getEpiDeploymentSplat = @{
ProjectId = “$(ProjectId)”
}

$deploy = Get-EpiDeployment @getEpiDeploymentSplat | Where-Object { $_.Status -eq ‘AwaitingVerification’ -and $_.parameters.targetEnvironment -eq ‘Integration’}
$deploy
if (-not $deploy) {
Write-Output “Environment Integration is not in status AwaitingVerification. We do not need to reset this environment.”
$deploymentId = “”
}
else {
$deploymentId = $deploy.id
}

#Start check if we should reset this environment.
if ($deploymentId.length -gt 1) {

$status = Get-EpiDeployment -ProjectId “$(ProjectId)” -Id $deploymentId
$status

if ($status.status -eq “AwaitingVerification”) {
Write-Host “Start Reset-EpiDeployment -ProjectId $(ProjectId) -Id $deploymentId”
Reset-EpiDeployment -ProjectId “$(ProjectId)” -Id $deploymentId

$percentComplete = $status.percentComplete
$status = Progress -projectid “$(ProjectId)” -deploymentId $deploymentId -percentComplete $percentComplete -expectedStatus “Reset” -timeout $timeout

if ($status.status -eq “Reset”) {
Write-Host “Deployment $deploymentId has been successfuly reset.”
Write-Host “##vso[task.logissue type=error]Deployment $deploymentId has been successfuly reset. But we can not continue deploy when we have reset the deployment.”
Write-Error “Deployment $deploymentId has been successfuly reset. But we can not continue deploy when we have reset the deployment.” -ErrorAction Stop
exit 1
}
else {
Write-Warning “The reset has not been successful or the script has timedout. CurrentStatus: $($status.status)”
Write-Host “##vso[task.logissue type=error]The reset has not been successful or the script has timedout. CurrentStatus: $($status.status)”
Write-Error “Deployment $deploymentId has NOT been successfuly reset or the script has timedout. CurrentStatus: $($status.status)” -ErrorAction Stop
exit 1
}
}
elseif ($status.status -eq “Reset”) {
Write-Host “The deployment $deploymentId is already in reset status.”
Write-Host “##vso[task.logissue type=error]Deployment $deploymentId is already in reset status. But we can not continue deploy when we have found errors in the smoke test.”
Write-Error “Deployment $deploymentId is already in reset status. But we can not continue deploy when we have found errors in the smoke test.” -ErrorAction Stop
exit 1
}
else {
Write-Host “Status is not in AwaitingVerification (Current:$($status.status)). You can not reset the deployment at this moment.”
Write-Host “##vso[task.logissue type=error]Status is not in AwaitingVerification (Current:$($status.status)). You can not reset the deployment at this moment.”
Write-Error “Status is not in AwaitingVerification (Current:$($status.status)). You can not reset the deployment at this moment.” -ErrorAction Stop
exit 1
}
}
}
else {
Write-Host “The deployment $deploymentId will not be reset. Smoketest is success.”
}

Write-Host “—THE END—“
4Deploy Package to Live PowerShell
Task version:2
Type: Inline
Script:
$rootPath = “$env:System_DefaultWorkingDirectory_ Name_Of_Your_Azure_Directory-ASP.NET-CI\DXC Deployment Package\”

if (-not (Get-Module -Name EpiCloud -ListAvailable)) {
Install-Module EpiCloud -Scope CurrentUser -Force
}

$completeEpiDeploymentSplat = @
ProjectId = “$(ProjectId)”
Id = “$env:DeploymentId”
Wait = $true
ClientKey = “$(PreProduction.ClientKey)”
ClientSecret = “$( PreProduction .ClientSecret)”

}

Complete-EpiDeployment @completeEpiDeploymentSplat

In this case, we are making the deployment to pre production which requires several variables to be defined and the target environment in step 2 to be Preproduction, that could also be a variable.

The variables required for the scripts are the following:

Parameter NameDescriptionValue
PreProduction.ClientKeyKey from Paas portal
PreProduction.ClientSecretSecret from Paas portal
ProjectIdId from Paas portal
RetriesNumber of retries to test if slot is working5
SleepBeforeRetrySleep n seconds before a retry30
SleepBeforeStartSleep n seconds before start the testing60
UrlsUrl to test, must have the slot post fix https://domainprep-slot.dxcloud.episerver.net/
UseMaintenancePageIf we are going to display the maintenance page while deploying$True

With all these set now you can use the build pipeline to generate a package for a specific branch, ex. a hotfix and the use the release pipeline to send it to the environment you want, in this case preproduction. You can create 1 more release pipeline using this as base and changing some of the variables to be able to deploy to production.

The azure directory name of your project Name_Of_Your_Azure_Directory can be found at the top of your build pipeline, as shown in the image below.

To get the client key, secret and the project id you can go to the Optimizely Paas portal, then to the API Tab and then to the deployment API credentials. If you do not have an api key yet you can press the Add API credentials to create one.

And that is it, You can now send hot fixes using azure pipelines and the deployment API without having to use the Pass portal. If you have any question let me know. I hope it will help someone and as always keep learning !!!

Written by:

Jorge Cardenas

Developer with several years of experience who is passionate about technology and how to solve problems through it.

View All Posts

Leave a Reply