This repository has been archived by the owner on May 16, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 51
/
Deploy.ps1
339 lines (286 loc) · 16.5 KB
/
Deploy.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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT license.
# --------------------------------------------------------------------------------------------
[CmdletBinding()]
param (
[string]$ResourceGroupName = "MSLearnLTI",
[string]$AppName = "MS-Learn-Lti-Tool-App",
[switch]$UseActiveAzureAccount,
[string]$SubscriptionNameOrId = $null,
[string]$LocationName = $null
)
process {
function Write-Title([string]$Title) {
Write-Host "`n`n============================================================="
Write-Host $Title
Write-Host "=============================================================`n`n"
}
try {
#region Show Learn LTI Banner
Write-Host ''
Write-Host ' _ ______ _____ _ _ _ _______ _____ '
Write-Host '| | | ____| /\ | __ \| \ | | | | |__ __|_ _|'
Write-Host '| | | |__ / \ | |__) | \| | ______ | | | | | | '
Write-Host '| | | __| / /\ \ | _ /| . ` | |______| | | | | | | '
Write-Host '| |____| |____ / ____ \| | \ \| |\ | | |____| | _| |_ '
Write-Host '|______|______/_/ \_\_| \_\_| \_| |______|_| |_____|'
Write-Host ''
Write-Host ''
#endregion
#region Setup Logging
. .\Write-Log.ps1
$ScriptPath = split-path -parent $MyInvocation.MyCommand.Definition
$ExecutionStartTime = $(get-date -f dd-MM-yyyy-HH-mm-ss)
$LogRoot = Join-Path $ScriptPath "Log"
$LogFile = Join-Path $LogRoot "Log-$ExecutionStartTime.log"
Set-LogFile -Path $LogFile
$TranscriptFile = Join-Path $LogRoot "Transcript-$ExecutionStartTime.log"
Start-Transcript -Path $TranscriptFile;
#endregion
# Checking Azure CLI is installed
$azureVersion = (az version 2>&1 | ConvertFrom-Json)."azure-cli"
if ($azureVersion -eq $null){
throw "Azure CLI is not installed and please go to this link to install. (https://learn.microsoft.com/en-gb/cli/azure/install-azure-cli?view=azure-cli-latest?WT.mc_id=academic-80547-leestott)"
}
if ($azureVersion -contains '2.27'){
throw "Please use the supported version of cli (2.27)"
}
# Checking .Net core 3.1 Framework is installed
$NetFrCheck= (dotnet --list-sdks | Select-String "3.1")
if( $NetFrCheck -eq $null){
throw "Needed .Net Framework is not installed and please go to this link to install '.Net core 3.1 version'. (https://dotnet.microsoft.com/download/dotnet/3.1?WT.mc_id=academic-80547-leestott)"
}
# Checking Node.js is installed
$NodejsCheck= (node -v)
if( $NodejsCheck -eq $null){
throw "Node.js is not installed and please go to this link to install. (https://nodejs.org/en/download/)"
}
else {
Write-Host "Node.js $NodejsCheck is installed."
}
# Checking Git is installed
$GitCheck= (git --version)
if( $GitCheck -eq $null){
throw "Git is not installed and please go to this link to install. (https://git-scm.com/downloads)"
}
else {
Write-Host "$GitCheck is installed."
}
Write-Title "Confirming all pre-requisites are installed."
#region Login to Azure CLI
Write-Title 'STEP #1 - Logging into Azure'
function Test-LtiActiveAzAccount {
$account = az account show | ConvertFrom-Json
if(!$account) {
throw "Error while trying to get Active Account Info."
}
}
function Connect-LtiAzAccount {
$loginOp = az login | ConvertFrom-Json
if(!$loginOp) {
throw "Encountered an Error while trying to Login."
}
}
if ($UseActiveAzureAccount) {
Write-Log -Message "Using Active Azure Account"
Test-LtiActiveAzAccount
}
else {
Write-Log -Message "Logging in to Azure"
Connect-LtiAzAccount
}
Write-Log -Message "Successfully logged in to Azure."
#endregion
#region Choose Active Subcription
Write-Title 'STEP #2 - Choose Subscription'
function Get-LtiSubscriptionList {
$AzAccountList = ((az account list --all --output json) | ConvertFrom-Json)
if(!$AzAccountList) {
throw "Encountered an Error while trying to fetch Subscription List."
}
Write-Output $AzAccountList
}
function Set-LtiActiveSubscription {
param (
[string]$NameOrId,
$List
)
$subscription = ($List | Where-Object { ($_.name -ieq $NameOrId) -or ($_.id -ieq $NameOrId) })
if(!$subscription) {
throw "Invalid Subscription Name/ID Entered."
}
az account set --subscription $NameOrId
#Intentionally not catching an exception here since the set subscription commands behavior (output) is different from others
Write-Output $subscription
}
Write-Log -Message "Fetching List of Subscriptions in Users Account"
$SubscriptionList = Get-LtiSubscriptionList
Write-Log -Message "List of Subscriptions:-`n$($SubscriptionList | ConvertTo-Json -Compress)"
$SubscriptionCount = ($SubscriptionList | Measure-Object).Count
Write-Log -Message "Count of Subscriptions: $SubscriptionCount"
if ($SubscriptionCount -eq 0) {
throw "Please create at least ONE Subscription in your Azure Account"
}
elseif ($SubscriptionNameOrId) {
Write-Log -Message "Using User provided Subscription Name/ID: $SubscriptionNameOrId"
}
elseif ($SubscriptionCount -eq 1) {
$SubscriptionNameOrId = $SubscriptionList[0].id;
Write-Log -Message "Defaulting to Subscription ID: $SubscriptionNameOrId"
}
else {
$SubscriptionListOutput = $SubscriptionList | Select-Object @{ l="Subscription Name"; e={ $_.name } }, "id", "isDefault"
Write-Host ($SubscriptionListOutput | Out-String)
$SubscriptionNameOrId = Read-Host 'Enter the Name or ID of the Subscription from Above List'
#trimming the input for empty spaces, if any
$SubscriptionNameOrId = $SubscriptionNameOrId.Trim()
Write-Log -Message "User Entered Subscription Name/ID: $SubscriptionNameOrId"
}
$ActiveSubscription = Set-LtiActiveSubscription -NameOrId $SubscriptionNameOrId -List $SubscriptionList
$UserEmailAddress = $ActiveSubscription.user.name
#endregion
#region Choose Region for Deployment
Write-Title "STEP #3 - Choose Location`n(Please refer to the Documentation / ReadMe on Github for the List of Supported Locations)"
Write-Log -Message "Fetching List of Locations"
$LocationList = ((az account list-locations) | ConvertFrom-Json)
Write-Log -Message "List of Locations:-`n$($locationList | ConvertTo-Json -Compress)"
if(!$LocationName) {
Write-Host "$(az account list-locations --output table --query "[].{Name:name}" | Out-String)`n"
$LocationName = Read-Host 'Enter Location From Above List for Resource Provisioning'
#trimming the input for empty spaces, if any
$LocationName = $LocationName.Trim()
}
Write-Log -Message "User Provided Location Name: $LocationName"
$ValidLocation = $LocationList | Where-Object { $_.name -ieq $LocationName }
if(!$ValidLocation) {
throw "Invalid Location Name Entered."
}
#endregion
#region Create New App Registration in AzureAD
Write-Title 'STEP #4 - Registering Azure Active Directory App'
Write-Log -Message "Creating AAD App with Name: $AppName"
$appinfo=$(az ad app create --display-name $AppName) | ConvertFrom-Json;
if(!$appinfo) {
throw "Encountered an Error while creating AAD App"
}
$identifierURI = "api://$($appinfo.appId)";
Write-Log -Message "Updating Identifier URI's in AAD App to: [ api://$($appinfo.appId) ]"
$appUpdateOp = az ad app update --id $appinfo.appId --identifier-uris $identifierURI;
#Intentionally not catching an exception here since the app update commands behavior (output) is different from others
Write-Log -Message "Updating App so as to add MS Graph -> User Profile -> Read Permissions to the AAD App"
$GraphAPIId = '00000003-0000-0000-c000-000000000000'
$GraphAPIPermissionId = 'e1fe6dd8-ba31-4d61-89e7-88639da4683d=Scope'
$appPermissionAddOp = az ad app permission add --id $appinfo.appId --api $GraphAPIId --api-permissions $GraphAPIPermissionId
#Intentionally not catching an exception here
Write-Host 'App Created Successfully'
#endregion
#region Create New Resource Group in above Region
Write-Title 'STEP #5 - Creating Resource Group'
Write-Log -Message "Creating Resource Group with Name: $ResourceGroupName at Location: $LocationName"
$resourceGroupCreationOp = az group create -l $LocationName -n $ResourceGroupName
if(!$resourceGroupCreationOp) {
$ex = @(
"Error while creating Resource Group with Name : [ $ResourceGroupName ] at Location: [ $LocationName ]."
"One Reason could be that a Resource Group with same name but different location already exists in your Subscription."
"Delete the other Resource Group and run this script again."
) -join ' '
throw $ex
}
Write-Host 'Resource Group Created Successfully'
#endregion
#region Provision Resources inside Resource Group on Azure using ARM template
Write-Title 'STEP #6 - Creating Resources in Azure'
$userObjectId = az ad signed-in-user show --query id
#$userObjectId
$templateFileName = "azuredeploy.json"
$deploymentName = "Deployment-$ExecutionStartTime"
Write-Log -Message "Deploying ARM Template to Azure inside ResourceGroup: $ResourceGroupName with DeploymentName: $deploymentName, TemplateFile: $templateFileName, AppClientId: $($appinfo.appId), IdentifiedURI: $($appinfo.identifierUris)"
$deploymentOutput = (az deployment group create --resource-group $ResourceGroupName --name $deploymentName --template-file $templateFileName --parameters appRegistrationClientId=$($appinfo.appId) appRegistrationApiURI=$($identifierURI) userEmailAddress=$($UserEmailAddress) userObjectId=$($userObjectId)) | ConvertFrom-Json;
if(!$deploymentOutput) {
throw "Encountered an Error while deploying to Azure"
}
Write-Host 'Resource Creation in Azure Completed Successfully'
Write-Title 'Step #7 - Updating KeyVault with LTI 1.3 Key'
function Update-LtiFunctionAppSettings([string]$ResourceGroupName, [string]$FunctionAppName, [hashtable]$AppSettings) {
Write-Log -Message "Updating App Settings for Function App [ $FunctionAppName ]: -"
foreach ($it in $AppSettings.GetEnumerator()) {
Write-Log -Message "`t[ $($it.Name) ] = [ $($it.Value) ]"
az functionapp config appsettings set --resource-group $ResourceGroupName --name $FunctionAppName --settings "$($it.Name)=$($it.Value)"
}
}
#Creating EdnaLiteDevKey in keyVault and Updating the Config Entry EdnaLiteDevKey in the Function Config
$keyCreationOp = (az keyvault key create --vault-name $deploymentOutput.properties.outputs.KeyVaultName.value --name EdnaLiteDevKey --protection software) | ConvertFrom-Json;
if(!$keyCreationOp) {
throw "Encountered an Error while creating Key in keyVault"
}
$KeyVaultLink = $keyCreationOp.key.kid
$EdnaKeyString = @{ "EdnaKeyString"="$KeyVaultLink" }
$ConnectUpdateOp = Update-LtiFunctionAppSettings $ResourceGroupName $deploymentOutput.properties.outputs.ConnectFunctionName.value $EdnaKeyString
$PlatformsUpdateOp = Update-LtiFunctionAppSettings $ResourceGroupName $deploymentOutput.properties.outputs.PlatformsFunctionName.value $EdnaKeyString
$UsersUpdateOp = Update-LtiFunctionAppSettings $ResourceGroupName $deploymentOutput.properties.outputs.UsersFunctionName.value $EdnaKeyString
Write-Host 'Key Creation in KeyVault Completed Successfully'
Write-Title 'Step #8 - Enabling Static Website Container'
#Creating a Container in Static Website
$containerEnableOp = az storage blob service-properties update --account-name $deploymentOutput.properties.outputs.StaticWebSiteName.value --static-website --404-document index.html --index-document index.html --only-show-errors
if(!$containerEnableOp) {
throw "Encountered an Error while creating Container to host Static Website"
}
Write-Host 'Static Website Container Enabled Successfully'
Write-Title 'STEP #9 - Updating AAD App'
$AppRedirectUrl = $deploymentOutput.properties.outputs.webClientURL.value
Write-Log -Message "Updating App with ID: $($appinfo.appId) to Redirect URL: $AppRedirectUrl and also enabling Implicit Flow"
$appUpdateRedirectUrlOp = az ad app update --id $appinfo.appId --reply-urls $AppRedirectUrl --oauth2-allow-implicit-flow true
#Intentionally not catching an exception here since the app update commands behavior (output) is different from others
Write-Host 'App Update Completed Successfully'
#endregion
#region Build and Publish Function Apps
. .\Install-Backend.ps1
Write-Title "STEP #10 - Installing the backend"
$BackendParams = @{
SourceRoot="../backend";
ResourceGroupName=$ResourceGroupName;
LearnContentFunctionAppName=$deploymentOutput.properties.outputs.LearnContentFunctionName.value;
LinksFunctionAppName=$deploymentOutput.properties.outputs.LinksFunctionName.value;
AssignmentsFunctionAppName=$deploymentOutput.properties.outputs.AssignmentsFunctionName.value;
ConnectFunctionAppName=$deploymentOutput.properties.outputs.ConnectFunctionName.value;
PlatformsFunctionAppName=$deploymentOutput.properties.outputs.PlatformsFunctionName.value;
UsersFunctionAppName=$deploymentOutput.properties.outputs.UsersFunctionName.value;
}
Install-Backend @BackendParams
#endregion
#region Build and Publish Client Artifacts
. .\Install-Client.ps1
Write-Title "STEP #11 - Updating client's .env.production file"
$ClientUpdateConfigParams = @{
ConfigPath="../client/.env.production";
AppId=$appinfo.appId;
LearnContentFunctionAppName=$deploymentOutput.properties.outputs.LearnContentFunctionName.value;
LinksFunctionAppName=$deploymentOutput.properties.outputs.LinksFunctionName.value;
AssignmentsFunctionAppName=$deploymentOutput.properties.outputs.AssignmentsFunctionName.value;
PlatformsFunctionAppName=$deploymentOutput.properties.outputs.PlatformsFunctionName.value;
UsersFunctionAppName=$deploymentOutput.properties.outputs.UsersFunctionName.value;
StaticWebsiteUrl=$deploymentOutput.properties.outputs.webClientURL.value;
}
Update-ClientConfig @ClientUpdateConfigParams
Write-Title 'STEP #12 - Installing the client'
$ClientInstallParams = @{
SourceRoot="../client";
StaticWebsiteStorageAccount=$deploymentOutput.properties.outputs.StaticWebSiteName.value
}
Install-Client @ClientInstallParams
#endregion
Write-Title "TOOL REGISTRATION URL (Please Copy, Required for Next Steps) -> $($deploymentOutput.properties.outputs.webClientURL.value)platform"
Write-Title '======== Successfully Deployed Resources to Azure ==========='
Write-Log -Message "Deployment Complete"
}
catch {
$Message = 'Error occurred while executing the Script. Please report the bug on Github (along with Error Message & Logs)'
Write-Log -Message $Message -ErrorRecord $_
throw $_
}
finally {
Stop-Transcript
$exit = Read-Host 'Press any Key to Exit'
}
}