Repository navigation
Expand file tree
/
Copy pathpost_deployment_setup.ps1
More file actions
285 lines (258 loc) · 12.5 KB
/
Copy pathpost_deployment_setup.ps1
File metadata and controls
285 lines (258 loc) · 12.5 KB
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
#Requires -Version 5.1
<#
.SYNOPSIS
Post-deployment setup script for v2.
.DESCRIPTION
Discovers resources from the resource group, handles WAF/private networking
(temporarily enables public access), sets env vars, delegates to post_provision.py,
then restores the original network state.
.PARAMETER ResourceGroupName
The name of the Azure resource group containing the deployed resources.
.EXAMPLE
.\post_deployment_setup.ps1 -ResourceGroupName "my-rg"
#>
param(
[Parameter(Position = 0)]
[string]$ResourceGroupName
)
$ErrorActionPreference = "Stop"
if (-not $ResourceGroupName) {
# Try to read from .azure/<env>/.env file
# Script is at infra/scripts/post-provision/ — go up 4 levels from file to reach repo root
$repoRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)))
$azureDir = Join-Path $repoRoot ".azure"
if (Test-Path $azureDir) {
$envFiles = Get-ChildItem -Path $azureDir -Recurse -Filter ".env" -File
foreach ($envFile in $envFiles) {
$match = Select-String -Path $envFile.FullName -Pattern '^AZURE_RESOURCE_GROUP="?([^"]+)"?$'
if ($match) {
$ResourceGroupName = $match.Matches[0].Groups[1].Value
break
}
}
}
# Try AZURE_RESOURCE_GROUP env var
if (-not $ResourceGroupName) {
$ResourceGroupName = $env:AZURE_RESOURCE_GROUP
}
# Prompt as last resort
if (-not $ResourceGroupName) {
$ResourceGroupName = Read-Host "Enter the resource group name"
if (-not $ResourceGroupName) {
Write-Error "Resource group name is required."
exit 1
}
}
}
Write-Host ""
Write-Host "=============================================="
Write-Host " Post-Deployment Setup (v2)"
Write-Host " Resource Group: $ResourceGroupName"
Write-Host "=============================================="
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
# -------------------------------------------------------
# Track resources that need public access restored
# -------------------------------------------------------
$RestorePgName = ""
$ServerName = ""
function Invoke-Cleanup {
# Remove temporary firewall rule
if ($ServerName) {
Write-Host "[OK] Removing temporary firewall rule..."
az postgres flexible-server firewall-rule delete `
--resource-group $ResourceGroupName `
--server-name $ServerName `
--name "AllowPostDeploySetup" `
--yes 2>$null | Out-Null
}
# Restore public access to Disabled on PostgreSQL
if ($RestorePgName) {
Write-Host "[OK] Disabling public access on PostgreSQL '$RestorePgName'..."
$null = az postgres flexible-server update --resource-group $ResourceGroupName `
--name $RestorePgName --public-access Disabled 2>$null
if ($LASTEXITCODE -ne 0) {
Write-Warning "Failed to disable public access on PostgreSQL. Please disable manually."
}
}
}
try {
# -------------------------------------------------------
# Discover resources and export env vars for post_provision.py
# -------------------------------------------------------
$env:AZURE_RESOURCE_GROUP = $ResourceGroupName
# PostgreSQL
$ServerFqdn = az postgres flexible-server list --resource-group $ResourceGroupName `
--query "[0].fullyQualifiedDomainName" -o tsv 2>$null
if ($LASTEXITCODE -ne 0) { $ServerFqdn = "" }
if ($ServerFqdn) {
$ServerName = $ServerFqdn.Split('.')[0]
$env:AZURE_POSTGRES_HOST = $ServerFqdn
$env:AZURE_POSTGRES_NAME = $ServerName
$env:AZURE_DB_TYPE = "postgresql"
# Clear any stale CosmosDB-mode vars from previous runs
$env:AZURE_AI_SEARCH_ENDPOINT = ""
Write-Host "[OK] Discovered PostgreSQL: $ServerName ($ServerFqdn)"
# --- WAF / Private Networking handling ---
$PgPublicAccess = az postgres flexible-server show --resource-group $ResourceGroupName `
--name $ServerName --query "network.publicNetworkAccess" -o tsv 2>$null
if ($LASTEXITCODE -ne 0) { $PgPublicAccess = "" }
if ($PgPublicAccess -eq "Disabled") {
Write-Host "PostgreSQL has public access disabled (private networking detected)."
Write-Host "[OK] Temporarily enabling public access on PostgreSQL '$ServerName'..."
$PgErr = az postgres flexible-server update --resource-group $ResourceGroupName `
--name $ServerName --public-access Enabled 2>&1
if ($PgErr -match "(?i)error") {
Write-Error "Failed to enable public access on PostgreSQL.`n $PgErr"
exit 1
}
$RestorePgName = $ServerName
Write-Host "Waiting for PostgreSQL network change to propagate..."
Start-Sleep -Seconds 30
}
# Wait for PostgreSQL server to be ready
Write-Host "Waiting for PostgreSQL server to be ready..."
$MaxRetries = 30
$RetryInterval = 30
for ($i = 1; $i -le $MaxRetries; $i++) {
$PgState = az postgres flexible-server show --resource-group $ResourceGroupName `
--name $ServerName --query "state" -o tsv 2>$null
if ($PgState -eq "Ready") {
Write-Host "PostgreSQL server is ready."
break
}
Write-Host " [$i/$MaxRetries] Server not ready (state: $PgState). Retrying in ${RetryInterval}s..."
Start-Sleep -Seconds $RetryInterval
}
# Add temporary firewall rule for deployer's IP
try {
$PublicIp = (Invoke-RestMethod -Uri "https://api.ipify.org" -TimeoutSec 10).Trim()
} catch {
$PublicIp = ""
}
if ($PublicIp) {
Write-Host "[OK] Adding temporary firewall rule for IP $PublicIp..."
az postgres flexible-server firewall-rule create `
--resource-group $ResourceGroupName `
--server-name $ServerName `
--name "AllowPostDeploySetup" `
--start-ip-address $PublicIp `
--end-ip-address $PublicIp 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Warning "Firewall rule creation may have failed."
}
}
} else {
$env:AZURE_DB_TYPE = "cosmosdb"
Write-Host "[OK] No PostgreSQL found; assuming CosmosDB mode."
# AI Search (CosmosDB mode only — PostgreSQL uses pgvector for indexing)
$SearchName = az search service list --resource-group $ResourceGroupName `
--query "[0].name" -o tsv 2>$null
if ($LASTEXITCODE -ne 0) { $SearchName = "" }
if ($SearchName) {
$env:AZURE_AI_SEARCH_ENDPOINT = "https://$SearchName.search.windows.net"
Write-Host "[OK] Discovered AI Search: $SearchName"
}
}
# OpenAI / AI Services (for knowledge base seed)
$aiQuery = '[?kind==`AIServices` || kind==`OpenAI`] | [0].properties.endpoint'
$AiServicesEndpoint = az cognitiveservices account list --resource-group $ResourceGroupName --query $aiQuery -o tsv 2>$null
if ($LASTEXITCODE -ne 0) { $AiServicesEndpoint = "" }
if ($AiServicesEndpoint) {
$env:AZURE_AI_SERVICES_ENDPOINT = $AiServicesEndpoint
$env:AZURE_OPENAI_ENDPOINT = $AiServicesEndpoint
Write-Host "[OK] Discovered AI Services: $AiServicesEndpoint"
}
# GPT deployment (first deployment that isn't an embedding model)
$aiNameQuery = '[?kind==`AIServices` || kind==`OpenAI`] | [0].name'
$AiAccountName = az cognitiveservices account list --resource-group $ResourceGroupName --query $aiNameQuery -o tsv 2>$null
if ($AiAccountName) {
$gptQuery = '[?contains(properties.model.name,`gpt`) || contains(properties.model.name,`o1`) || contains(properties.model.name,`o3`) || contains(properties.model.name,`o4-mini`)] | [0].name'
$GptDeployment = az cognitiveservices account deployment list `
--resource-group $ResourceGroupName `
--name $AiAccountName `
--query $gptQuery -o tsv 2>$null
if ($LASTEXITCODE -ne 0) { $GptDeployment = "" }
if ($GptDeployment) {
$env:AZURE_OPENAI_GPT_DEPLOYMENT = $GptDeployment
Write-Host "[OK] Discovered GPT deployment: $GptDeployment"
}
}
# Fallback for BYO / cross-subscription Foundry: the OpenAI account and GPT
# model live outside this resource group, so the in-RG lookups above find
# nothing. Read the authoritative endpoint and deployment the backend
# container app was deployed with.
if (-not $AiServicesEndpoint -or -not $GptDeployment) {
$BackendName = az containerapp list --resource-group $ResourceGroupName `
--query "[?contains(name,'backend')] | [0].name" -o tsv 2>$null
if ($LASTEXITCODE -ne 0) { $BackendName = "" }
if ($BackendName) {
if (-not $AiServicesEndpoint) {
$AiServicesEndpoint = az containerapp show --resource-group $ResourceGroupName --name $BackendName `
--query "properties.template.containers[0].env[?name=='AZURE_OPENAI_ENDPOINT'].value | [0]" -o tsv 2>$null
if ($LASTEXITCODE -ne 0) { $AiServicesEndpoint = "" }
if ($AiServicesEndpoint) {
$env:AZURE_AI_SERVICES_ENDPOINT = $AiServicesEndpoint
$env:AZURE_OPENAI_ENDPOINT = $AiServicesEndpoint
Write-Host "[OK] Discovered AI Services from backend app '$BackendName': $AiServicesEndpoint"
}
}
if (-not $GptDeployment) {
$GptDeployment = az containerapp show --resource-group $ResourceGroupName --name $BackendName `
--query "properties.template.containers[0].env[?name=='AZURE_OPENAI_GPT_DEPLOYMENT'].value | [0]" -o tsv 2>$null
if ($LASTEXITCODE -ne 0) { $GptDeployment = "" }
if ($GptDeployment) {
$env:AZURE_OPENAI_GPT_DEPLOYMENT = $GptDeployment
Write-Host "[OK] Discovered GPT deployment from backend app '$BackendName': $GptDeployment"
}
}
}
}
# Deployer UPN
$DeployerUpn = az ad signed-in-user show --query "userPrincipalName" -o tsv 2>$null
if ($LASTEXITCODE -ne 0) { $DeployerUpn = "" }
if ($DeployerUpn) {
$env:AZURE_POSTGRES_DEPLOYER_PRINCIPAL_NAME = $DeployerUpn
}
# -------------------------------------------------------
# Grant the deployer Container Apps Contributor at the resource group so the
# Microsoft identity provider (Easy Auth) can be configured manually on any
# container app in the RG. A direct (non-group) grant sidesteps the
# group-based-RBAC listSecrets validation failure. Idempotent.
# -------------------------------------------------------
$DeployerObjectId = az ad signed-in-user show --query "id" -o tsv 2>$null
if ($LASTEXITCODE -ne 0) { $DeployerObjectId = "" }
$ResourceGroupId = az group show --name $ResourceGroupName --query "id" -o tsv 2>$null
if ($LASTEXITCODE -ne 0) { $ResourceGroupId = "" }
if ($DeployerObjectId -and $ResourceGroupId) {
Write-Host "[OK] Granting Container Apps Contributor to deployer on '$ResourceGroupName'..."
az role assignment create `
--assignee-object-id $DeployerObjectId `
--assignee-principal-type User `
--role "358470bc-b998-42bd-ab17-a7e34c199c0f" `
--scope $ResourceGroupId 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Warning "Could not grant Container Apps Contributor (needs Owner or User Access Administrator). Configuring Easy Auth manually may fail until this role is assigned."
}
}
Write-Host ""
Write-Host "--- Running post_provision.py ---"
Write-Host ""
# Run the Python script (uses uv if available, falls back to python)
if (Get-Command uv -ErrorAction SilentlyContinue) {
uv run python "$ScriptDir\post_provision.py"
} elseif (Get-Command python3 -ErrorAction SilentlyContinue) {
python3 "$ScriptDir\post_provision.py"
} else {
python "$ScriptDir\post_provision.py"
}
if ($LASTEXITCODE -ne 0) {
Write-Error "post_provision.py failed with exit code $LASTEXITCODE"
exit $LASTEXITCODE
}
Write-Host ""
Write-Host "=============================================="
Write-Host " Post-Deployment Setup Complete"
Write-Host "=============================================="
} finally {
Invoke-Cleanup
}