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.
Step | Name | Type | Parameters |
---|---|---|---|
1 | Use NuGet 5.8.1 | NuGet tool installer | Task version: 0 Version of NuGet.exe to install: 5.8.1 |
2 | NuGet restore | NuGet | Task 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) |
3 | Build solution | Visual Studio build | Task 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 |
4 | Publish symbols path | Index sources and publish symbols | Task version: 2 Path to symbols folder: $(Build.SourcesDirectory) Search pattern: *\bin**.pdb Index sources: Checked Verbose logging: Checked Artifact name: Symbols_$(BuildConfiguration) |
5 | Archive | Archive files | Task 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 |
6 | Publish Artifact | Publish build artifacts | Task 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.
Step | Name | Type | Parameters |
---|---|---|---|
1 | Upload Package | PowerShell | 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 |
2 | Deploy 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)” |
3 | Smoke 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—“ |
4 | Deploy 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 Name | Description | Value |
---|---|---|
PreProduction.ClientKey | Key from Paas portal | – |
PreProduction.ClientSecret | Secret from Paas portal | – |
ProjectId | Id from Paas portal | – |
Retries | Number of retries to test if slot is working | 5 |
SleepBeforeRetry | Sleep n seconds before a retry | 30 |
SleepBeforeStart | Sleep n seconds before start the testing | 60 |
Urls | Url to test, must have the slot post fix | https://domainprep-slot.dxcloud.episerver.net/ |
UseMaintenancePage | If 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 !!!
Leave a Reply