-
Notifications
You must be signed in to change notification settings - Fork 0
/
OmniaUpdate.ps1
314 lines (270 loc) · 14 KB
/
OmniaUpdate.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
#Requires -Version 3.0
#Requires -Module AzureRM.Resources
#Requires -Module Azure.Storage
Param(
[string] [Parameter(Mandatory=$true)] $WebsiteName, #http or https://*.azurewebsites.net
[string] [Parameter(Mandatory=$true)] $SubscriptionName, #as in -subscriptionname param elsewhere
[string] [Parameter(Mandatory=$true)] $ResourceGroupName, #as in -resourcegroupname param elsewhere
[string] $ResourceNameString,
[string] $Version,
[string] $Slot = "Production",
[switch] $force = $false,
[switch] $whatIf = $false,
[string] $FeedURL = "https://mymiswebdeploy.blob.core.windows.net/platformversions/updateFeed.xml"
)
$ErrorActionPreference = "Stop"
$OutputEncoding = New-Object -typename System.Text.UTF8Encoding
$thisScriptVersion = 1.6
function Get-ScriptDirectory
{
#Obtains the executing directory of the script
$Invocation = (Get-Variable MyInvocation -Scope 1).Value;
if($Invocation.PSScriptRoot)
{
$Invocation.PSScriptRoot;
}
Elseif($Invocation.MyCommand.Path)
{
Split-Path $Invocation.MyCommand.Path
}
else
{
$Invocation.InvocationName.Substring(0,$Invocation.InvocationName.LastIndexOf("\"));
}
}
function BinariesUpdate
{
#Performs the update of the binaries on the desired instance of the platform.
param([string] $ResourceGroupName, [string] $Version, [string] $packageFolder, [bool] $whatIf = $false, [string] $Slot)
$templateUri = "$packageFolder"+"updateTemplate.json"
$templateParams = @{}
$siteName = (($WebsiteName -split "://")[1] -split ".azurewebsites.net")[0]
$templateParams.Add("siteName",$siteName);
$templateParams.Add("slotName", $Slot);
## Test template validity
$result = Test-AzureRmResourceGroupDeployment -ResourceGroupName $ResourceGroupName -TemplateUri $templateUri -TemplateParameterObject $templateParams
if (-not ($result.Count -eq 0)){
throw "Error validating deployment."
}
## If ok, perform it
Write-Host "Template is valid. Beginning update to version $Version on RG $ResourceGroupName, Website $siteName [$Slot]"
if (-not $whatIf){
Write-Progress -id 1 -activity "Deploying template" -Status "In Progress"
$deployment = New-AzureRmResourceGroupDeployment -ResourceGroupName $ResourceGroupName -TemplateUri $templateUri -TemplateParameterObject $templateParams
Write-Host ("Deployment finished. Status: " + $deployment.ProvisioningState)
Write-Progress -id 1 -activity "Deploying template" -Status "Completed" -Completed
}
else{
Write-Host ("Dry run. Deployment not performed.")
}
}
function GetCurrentVersion
{
#Obtains the current platform version. For old versions, it's an API parse - in future, should be switched to a call to the service implemented in 1.200.166 : api/v1/Version/Get
param([string] $WebsiteName)
Write-Progress -id 2 -activity "Obtaining version" -Status "In Progress"
try{ #Version is at least 1.200.166
$VersionStr = Invoke-RestMethod "$WebsiteName/api/v1/Version/Get" -UseBasicParsing
if (-not $VersionStr -or $VersionStr.Length -eq 0 -or $VersionStr -like "*not found*" -or $VersionStr -like "*Oops*"){
throw "API error - version not found"
}
}
catch{
try{
$apiData = Invoke-WebRequest "$WebsiteName/api" -UseBasicParsing
$startPos = ($apiData.Content.IndexOf("Platform - ")+("Platform - ").Length)
$versionLen = $apiData.Content.IndexOf("</h1>") - $startPos
$VersionStr = $apiData.Content.Substring($startPos,$versionLen)
if (-not $VersionStr -or $VersionStr.Length -eq 0 -or $VersionStr -like "*not found*" -or $VersionStr -like "*Oops*"){
throw "API error - version not found"
}
}
catch{
Write-Host "Failed obtaining version! Site may be failing. Assuming empty version."
$versionStr = "0.0.0"
}
}
Write-Progress -id 2 -activity "Obtaining version" -Status "Obtained" -Completed
return $VersionStr
}
function GetMigrationsList
{
#Obtains all of the migrations between the current and desired version, and verifies whether any of them are major changes (except for the desired version itself).
#If any major changes exists, stops the process.
param([string] $currentVersion, [System.Object[]] $versionList, [string] $Version )
try{
[array]$migrationList = $versionList | Select-Object @{name='Number';expression={(New-Object version $_."Number")}},PackageFolder,ExecuteScript,MajorChange | Where-Object Number -gt ([version] $currentVersion) | Where-Object Number -le ([version] $Version) | Where-Object ExecuteScript -Eq "true" | Sort-Object -Property Number
[array]$majorChanges = $versionList | Select-Object @{name='Number';expression={(New-Object version $_."Number")}},PackageFolder,ExecuteScript,MajorChange | Where-Object Number -gt ([version] $currentVersion) | Where-Object Number -lt ([version] $Version) | Where-Object MajorChange -Eq "true"
if ($majorChanges -and $majorChanges.Count -gt 0){
throw "Major breaking changes detected that will not allow for a one-step platform migration. Please perform manual migrations for the versions: " + $majorChanges.Number
}
return $migrationList
}
catch{
$PSCmdlet.WriteError($_)
return
}
}
function CreateTempFile
{
#Creates a temporary file in a folder.
param([string] $tempFolder, [string] $fileName, [string] $fileContents)
$newFile = "$tempFolder"+"\"+"$fileName"
Write-Host "Creating temporary file $newFile"
[System.IO.File]::WriteAllText($newFile, $fileContents, [System.Text.Encoding]::UTF8)
return $newFile
}
function PerformMigrations
{
#Invokes any scripts that need to be invoked.
param([System.Object[]] $migrationList, [string] $migrationArgs, [bool] $whatIf, [string] $Slot)
$siteName = (($WebsiteName -split "://")[1] -split ".azurewebsites.net")[0]
if ($migrationList -and $migrationList.Count -gt 0){
Write-Host ("Detected "+$migrationList.Count+" migrations with scripts. Applying...")
if (-not $whatIf){
Write-Progress -id 3 -activity "Applying migrations" -Status "In Progress"
ForEach ($obj In $migrationList){
$scriptUri = $obj.PackageFolder+"migrationScript.ps1"
$resp = (Invoke-WebRequest -Uri $scriptUri -Method GET -ContentType "application/octet-stream;charset=utf-8" -UseBasicParsing)
$migrationScript = [system.Text.Encoding]::UTF8.GetString($resp.RawContentStream.ToArray());
$tempPath = Get-ScriptDirectory #TODO: Investigate way to run scripts in memory without saving them
$fileLocation = CreateTempFile $tempPath "migrationScript.ps1" $migrationScript
Write-Host ("-------------------EXECUTING MIGRATION SCRIPT "+$obj.Number+" ---------------")
Invoke-Expression "& `"$fileLocation`" $migrationArgs"
Write-Host ("-------------------FINISHED EXECUTING MIGRATION SCRIPT "+$obj.Number+" ---------------")
}
#TODO: Delete file from $fileLocation
Write-Progress -id 3 -activity "Applying migrations" -Status "Migrations Finished, Waiting on Site" -PercentComplete 50
Start-Sleep -s 30 #To avoid race condition: see https://blogs.msdn.microsoft.com/hosamshobak/2016/05/26/arm-template-msdeploy-race-condition-issue/
Write-Progress -id 3 -activity "Applying migrations" -Status "Completed" -completed
}
else{
Write-Host ("Dry run. Migrations not performed.")
}
}
else{
Write-Host "No scripts necessary to execute. Just updating binaries."
}
}
function BuildMigrationArgs
{
#Creates an object that will be passed to all the scripts we execute, containing all the information we deem necessary.
param([string] $ResourceGroupName,[string] $subscriptionName,[string] $WebsiteName,[string] $Slot, [string] $ResourceNameString )
$migrationArgs = "-ResourceGroupName `"$ResourceGroupName`" -subscriptionName `"$subscriptionName`" -WebsiteName `"$WebsiteName`" -Slot `"$Slot`" -ResourceNameString `"$ResourceNameString`""
return $migrationArgs
}
function CompareVersions
{
#Compares the version you want to update to and the version of the platform, and notifies the user if they are re-updating or rolling back an update.
#If it returns false, we should not execute migration scripts.
param([version]$currentVersion, [version]$Version, [bool] $force)
if ($currentVersion -eq $Version){
Write-Host ("Current version is the same as the version you want to update to: $currentVersion") -ForegroundColor Yellow
if (-not $force){
$confirmation = Read-Host ("Do you want to stop the update process? N to continue")
if ($confirmation -ne 'n' -and $confirmation -ne 'no') {
Write-Host "Update process stopped due to user request."
Exit 0
}
}
else{
Write-Host ("-Force is set, stopping update") -ForegroundColor Yellow -BackgroundColor DarkMagenta
Exit 0
}
return $false
}
elseif ($currentVersion -gt $Version){
Write-Host ("Current version has a HIGHER VERSION NUMBER than the version you want to update to: ($currentVersion) -> ($Version)") -ForegroundColor Yellow
if (-not $force){
$confirmation = Read-Host ("Do you want to stop the update process? N to continue")
if ($confirmation -ne 'n' -and $confirmation -ne 'no') {
Write-host "Update process stopped due to user request."
Exit 0
}
}
else{
Write-Host ("-Force is set, stopping update") -ForegroundColor Yellow -BackgroundColor DarkMagenta
Exit 0
}
return $false
}
return $true
}
Write-Host "Omnia Platform update process - version $thisScriptVersion"
#Login-AzureRmAccount
Set-AzureRmContext -SubscriptionName $SubscriptionName
$siteName = (($WebsiteName -split "://")[1] -split ".azurewebsites.net")[0]
if (-not $ResourceNameString){
$ResourceNameString = ($siteName -split "wsmymis")[1]
}
$updateFeed = [xml](Invoke-WebRequest $FeedURL -UseBasicParsing).Content
## Version checks
$latestVersion = ($updateFeed.PlatformVersions.Version | Sort-Object @{e={$_.Number -as [version]}} -Descending)[0]
Write-Host "Got update feed. Latest version:" $latestVersion.Number
$currentVersion = GetCurrentVersion $WebsiteName
Write-Host "Current version of $WebsiteName is $currentVersion"
if ($slot -ne "Production"){
$slotSiteName = $siteName + "-" + $Slot + ".azurewebsites.net"
$currentSlotVersion = GetCurrentVersion $slotSiteName
Write-Host "Current version of $WebsiteName in the slot $Slot is $currentSlotVersion"
if ($currentVersion -ne $currentSlotVersion){
Write-Host "The current version in slot $slot is not the same as the version in production! This may cause issues with configuration migrations." -ForegroundColor Yellow
if (-not $force.IsPresent){
$confirmation = Read-Host ("Are you sure you want to upgrade the slot $slot even though it may cause configuration issues?")
if ($confirmation -ne 'y' -and $confirmation -ne 'yes') {
throw "Swap not performed. Please re-create the site in slot $slot as a copy of the production site first."
}
}
else{
Write-Host ("-Force is set, proceeding with swap") -ForegroundColor Yellow -BackgroundColor DarkMagenta
}
}
}
$migrationArgs = BuildMigrationArgs $ResourceGroupName $subscriptionName $WebsiteName $Slot $ResourceNameString
if ($Version -eq ""){
Write-Host "Going to update to the latest version."
$versionInfo = $latestVersion
}
else{
Write-Host "Searching for requested version $Version."
$versionInfo = $updateFeed.PlatformVersions.Version | Where-Object "Number" -eq $Version
if ($versionInfo){
Write-Host "Desired version found."
}
else{
throw "Requested platform version not found in update feed!"
}
}
$canExecuteMigrations = CompareVersions ([version]$currentVersion) ([version]$versionInfo.Number) $force
Write-Host "Checking for migrations with scripts..."
$migrationList = @(GetMigrationsList $currentVersion $updateFeed.PlatformVersions.Version $versionInfo.Number)
if ($canExecuteMigrations){
PerformMigrations $migrationList $migrationArgs $whatIf.IsPresent $Slot
}
BinariesUpdate $ResourceGroupName $versionInfo.Number $versionInfo.PackageFolder $whatIf.IsPresent $Slot
if ($Slot -ne "Production"){
if (-not $force.IsPresent){
$confirmation = Read-Host ("Are you sure you want to perform a swap from slot $Slot to slot Production?")
if ($confirmation -ne 'y' -and $confirmation -ne 'yes') {
Write-Host "Swap not performed. Please perform it manually via the Azure Portal." -ForegroundColor Yellow -BackgroundColor DarkMagenta
return
}
}
else{
Write-Host ("-Force is set, proceeding with swap") -ForegroundColor Yellow -BackgroundColor DarkMagenta
}
$siteName = (($WebsiteName -split "://")[1] -split ".azurewebsites.net")[0]
Write-Host "Beginning the swap between slot $Slot and slot Production."
Write-Progress -id 4 -activity "Performing Swap" -Status "Beginning Swap"
if (-not $whatIf.IsPresent){
Swap-AzureRmWebAppSlot -ResourceGroupName $ResourceGroupName -Name $siteName -SourceSlotName $Slot -DestinationSlotName production
}
Write-Progress -id 4 -activity "Performing Swap" -Status "Completed" -completed
}
if (-not $whatIf.IsPresent){
$finalVersion = GetCurrentVersion $WebsiteName
Write-Host "Current version of $WebsiteName after update is $finalVersion"
if ($finalVersion -ne $versionInfo.Number){
throw ("Update inconsistent! Version in site after update finishing, $finalVersion, not the same as the desired version, "+$versionInfo.Number)
}
}