99 - main
1010 - dev
1111 - demo
12- - vee-pipeline-fixes
1312 schedule :
1413 - cron : ' 0 5,17 * * *' # Runs at 5:00 AM and 5:00 PM GMT
1514 workflow_dispatch :
2423 outputs :
2524 RESOURCE_GROUP_NAME : ${{ steps.check_create_rg.outputs.RESOURCE_GROUP_NAME }}
2625 WEBAPP_URL : ${{ steps.get_output.outputs.WEBAPP_URL }}
27- SELECTED_AI_REGION : ${{ steps.deploy.outputs.selected_ai_region || env.VALID_REGION }}
2826 steps :
2927 - name : Checkout Code
3028 uses : actions/checkout@v3
7876 - name : Install Bicep CLI
7977 run : az bicep install
8078
81- # reverted: jq explicit installation step removed
82-
8379 - name : Generate Resource Group Name
8480 id : generate_rg_name
8581 run : |
@@ -130,24 +126,21 @@ jobs:
130126 IMAGE_TAG="latest"
131127 fi
132128
133- EFFECTIVE_AI_REGION="${VALID_REGION:-eastus}" # VALID_REGION exported earlier if quota script succeeded
134- echo "Using AI Deployments Region: $EFFECTIVE_AI_REGION"
135- echo "selected_ai_region=$EFFECTIVE_AI_REGION" >> $GITHUB_OUTPUT
136-
137- echo "Resource Group: ${{ env.RESOURCE_GROUP_NAME }} (Created in northcentralus)"
138- echo "Solution Prefix: ${{ env.SOLUTION_PREFIX }}"
139- echo "Image Tag: ${IMAGE_TAG}"
140-
129+ # Generate current timestamp in desired format: YYYY-MM-DDTHH:MM:SS.SSSSSSSZ
130+ current_date=$(date -u +"%Y-%m-%dT%H:%M:%S.%7NZ")
131+
141132 az deployment group create \
142133 --name ${{ env.SOLUTION_PREFIX }}-deployment \
143134 --resource-group ${{ env.RESOURCE_GROUP_NAME }} \
144135 --template-file infra/main.bicep \
145136 --parameters \
146137 solutionName="${{ env.SOLUTION_PREFIX }}" \
147- aiDeploymentsLocation="$EFFECTIVE_AI_REGION " \
138+ aiDeploymentsLocation="eastus " \
148139 capacity=${{ env.GPT_MIN_CAPACITY }} \
149140 imageVersion="${IMAGE_TAG}" \
150- createdBy="Pipeline"
141+ createdBy="Pipeline" \
142+ tags="{'SecurityControl':'Ignore','Purpose':'Deploying and Cleaning Up Resources for Validation','CreatedDate':'$current_date'}"
143+
151144 - name : Assign Contributor role to Service Principal
152145 if : always()
153146 run : |
@@ -165,20 +158,9 @@ jobs:
165158 echo "Fetching deployment output..."
166159 BICEP_OUTPUT=$(az deployment group show --name ${{ env.SOLUTION_PREFIX }}-deployment --resource-group ${{ env.RESOURCE_GROUP_NAME }} --query "properties.outputs" -o json)
167160 echo "Extracting deployment output..."
168- echo "Available output keys:"
169- echo "$BICEP_OUTPUT" | jq -r 'keys | join(", ")'
170- # Case-insensitive lookup for WEB_APP_URL because deployment outputs returned inconsistent casing previously
171- WEBAPP_URL=$(echo "$BICEP_OUTPUT" | jq -r 'to_entries | map(select((.key|ascii_upcase)=="WEB_APP_URL")) | .[0].value.value // empty')
172- if [ -z "$WEBAPP_URL" ] || [ "$WEBAPP_URL" = "null" ]; then
173- echo "WEB_APP_URL not found in deployment outputs (case-insensitive scan)" >&2
174- echo "Full outputs JSON follows for troubleshooting:" >&2
175- echo "$BICEP_OUTPUT" >&2
176- exit 1
177- fi
178- echo "Resolved WEBAPP_URL: $WEBAPP_URL"
161+ WEBAPP_URL=$(echo $BICEP_OUTPUT | jq -r '.weB_APP_URL.value')
179162 echo "WEBAPP_URL=$WEBAPP_URL" >> $GITHUB_OUTPUT
180- echo "Deployment outputs (truncated):"
181- echo "$BICEP_OUTPUT" | head -c 2000
163+ echo "Deployment output: $BICEP_OUTPUT"
182164
183165 - name : Logout from Azure
184166 if : always()
@@ -199,7 +181,6 @@ jobs:
199181 runs-on : ubuntu-latest
200182 env :
201183 RESOURCE_GROUP_NAME : ${{ needs.deploy.outputs.RESOURCE_GROUP_NAME }}
202- AI_REGION : ${{ needs.deploy.outputs.SELECTED_AI_REGION }}
203184 steps :
204185 - name : Setup Azure CLI
205186 run : |
@@ -209,7 +190,7 @@ jobs:
209190 - name : Login to Azure
210191 run : |
211192 az login --service-principal -u ${{ secrets.AZURE_CLIENT_ID }} -p ${{ secrets.AZURE_CLIENT_SECRET }} --tenant ${{ secrets.AZURE_TENANT_ID }}
212-
193+
213194 - name : Assign Contributor role to Service Principal
214195 if : always()
215196 run : |
@@ -219,34 +200,56 @@ jobs:
219200 --role "Contributor" \
220201 --scope /subscriptions/${{ secrets.AZURE_SUBSCRIPTION_ID }}/resourceGroups/${{ env.RESOURCE_GROUP_NAME }}
221202
203+
222204 - name : Get Log Analytics Workspace and OpenAI from Resource Group
223205 if : always()
224206 id : get_azure_resources
225207 run : |
208+
226209 set -e
227- echo "Collecting resource identifiers from RG ${{ env.RESOURCE_GROUP_NAME }} before deletion..."
228- log_analytics_workspace_name=$(az monitor log-analytics workspace list --resource-group ${{ env.RESOURCE_GROUP_NAME }} --query "[0].name" -o tsv || true)
229- if [ -n "$log_analytics_workspace_name" ]; then
210+ echo "Fetching Log Analytics workspace from resource group ${{ env.RESOURCE_GROUP_NAME }}..."
211+
212+ # Run the az monitor log-analytics workspace list command to get the workspace name
213+ log_analytics_workspace_name=$(az monitor log-analytics workspace list --resource-group ${{ env.RESOURCE_GROUP_NAME }} --query "[0].name" -o tsv)
214+
215+ if [ -z "$log_analytics_workspace_name" ]; then
216+ echo "No Log Analytics workspace found in resource group ${{ env.RESOURCE_GROUP_NAME }}."
217+ else
230218 echo "LOG_ANALYTICS_WORKSPACE_NAME=${log_analytics_workspace_name}" >> $GITHUB_ENV
219+ echo "Log Analytics workspace name : ${log_analytics_workspace_name}"
231220 fi
232- openai_resource_name=$(az resource list --resource-group ${{ env.RESOURCE_GROUP_NAME }} --resource-type "Microsoft.CognitiveServices/accounts" --query "[0].name" -o tsv || true)
233- if [ -n "$openai_resource_name" ]; then
221+
222+ echo "Fetching OpenAI resource from resource group ${{ env.RESOURCE_GROUP_NAME }}..."
223+
224+ # Run the az resource list command to get the OpenAI resource name
225+ openai_resource_name=$(az resource list --resource-group ${{ env.RESOURCE_GROUP_NAME }} --resource-type "Microsoft.CognitiveServices/accounts" --query "[0].name" -o tsv)
226+
227+ if [ -z "$openai_resource_name" ]; then
228+ echo "No OpenAI resource found in resource group ${{ env.RESOURCE_GROUP_NAME }}."
229+ exit 1
230+ else
234231 echo "OPENAI_RESOURCE_NAME=${openai_resource_name}" >> $GITHUB_ENV
232+ echo "OpenAI resource name : ${openai_resource_name}"
235233 fi
236- echo "Snapshot complete."
237234
238235 - name : List KeyVaults and Store in Array
239236 if : always()
240237 id : list_keyvaults
241238 run : |
239+
242240 set -e
243241 echo "Listing all KeyVaults in the resource group ${RESOURCE_GROUP_NAME}..."
244- keyvaults=$(az resource list --resource-group ${{ env.RESOURCE_GROUP_NAME }} --query "[?type=='Microsoft.KeyVault/vaults'].name" -o tsv || true)
242+
243+ # Get the list of KeyVaults in the specified resource group
244+ keyvaults=$(az resource list --resource-group ${{ env.RESOURCE_GROUP_NAME }} --query "[?type=='Microsoft.KeyVault/vaults'].name" -o tsv)
245+
245246 if [ -z "$keyvaults" ]; then
246247 echo "No KeyVaults found in resource group ${RESOURCE_GROUP_NAME}."
247- echo "KEYVAULTS=[]" >> $GITHUB_ENV
248+ echo "KEYVAULTS=[]" >> $GITHUB_ENV # If no KeyVaults found, set an empty array
248249 else
249250 echo "KeyVaults found: $keyvaults"
251+
252+ # Format the list into an array with proper formatting (no trailing comma)
250253 keyvault_array="["
251254 first=true
252255 for kv in $keyvaults; do
@@ -258,76 +261,151 @@ jobs:
258261 fi
259262 done
260263 keyvault_array="$keyvault_array]"
264+
265+ # Output the formatted array and save it to the environment variable
261266 echo "KEYVAULTS=$keyvault_array" >> $GITHUB_ENV
262267 fi
263- echo "Stored KeyVault array in KEYVAULTS environment variable."
264268
265- - name : Delete Resource Group (primary deletion)
269+ - name : Delete Bicep Deployment
266270 if : always()
267271 run : |
268- set -e
269- echo "Initiating deletion of RG ${{ env.RESOURCE_GROUP_NAME }}..."
270- az group delete --name ${{ env.RESOURCE_GROUP_NAME }} --yes --no-wait || echo "Delete command issued."
272+ set -e
273+ echo "Checking if resource group exists..."
274+ rg_exists=$(az group exists --name ${{ env.RESOURCE_GROUP_NAME }})
275+ if [ "$rg_exists" = "true" ]; then
276+ echo "Resource group exist. Cleaning..."
277+ az group delete \
278+ --name ${{ env.RESOURCE_GROUP_NAME }} \
279+ --yes \
280+ --no-wait
281+ echo "Resource group deleted... ${{ env.RESOURCE_GROUP_NAME }}"
282+ else
283+ echo "Resource group does not exists."
284+ fi
271285
272- - name : Wait for RG deletion propagation
286+ - name : Purge log analytics workspace
273287 if : always()
288+ id : log_analytics_workspace
274289 run : |
275- set -e
276- echo "Polling for RG deletion..."
277- max_retries=15
278- sleep_interval=20
279- for i in $(seq 1 $max_retries); do
280- exists=$(az group exists --name ${{ env.RESOURCE_GROUP_NAME }} || echo true)
281- if [ "$exists" = false ]; then
282- echo "Resource group deleted (confirmed)."
283- break
284- fi
285- echo "RG still exists (attempt $i). Waiting $sleep_interval s..."
286- sleep $sleep_interval
287- done
288290
289- - name : Purge soft-deleted OpenAI (if any)
290- if : always()
291- run : |
292291 set -e
293- if [ -n "${{ env.OPENAI_RESOURCE_NAME }}" ]; then
294- echo "Attempting purge of soft-deleted OpenAI: ${{ env.OPENAI_RESOURCE_NAME }} in region ${{ env.AI_REGION }}"
295- az resource delete --ids /subscriptions/${{ secrets.AZURE_SUBSCRIPTION_ID }}/providers/Microsoft.CognitiveServices/locations/${{ env.AI_REGION || 'eastus' }}/resourceGroups/${{ env.RESOURCE_GROUP_NAME }}/deletedAccounts/${{ env.OPENAI_RESOURCE_NAME }} --verbose || echo "OpenAI purge not available yet or failed (non-fatal)."
292+ # Purge Log Analytics Workspace
293+ echo "Purging the Log Analytics Workspace..."
294+ if ! az monitor log-analytics workspace delete --force --resource-group ${{ env.RESOURCE_GROUP_NAME }} --workspace-name ${{ env.LOG_ANALYTICS_WORKSPACE_NAME }} --yes --verbose; then
295+ echo "Failed to purge Log Analytics workspace: ${{ env.LOG_ANALYTICS_WORKSPACE_NAME }}"
296296 else
297- echo "No OpenAI resource recorded to purge. "
297+ echo "Purged the Log Analytics workspace: ${{ env.LOG_ANALYTICS_WORKSPACE_NAME }} "
298298 fi
299299
300- - name : Purge soft-deleted KeyVaults (if any)
300+ echo "Log analytics workspace resource purging completed successfully"
301+
302+
303+ - name : Wait for resource deletion to complete
301304 if : always()
302305 run : |
303- set -e
306+
307+ # List of keyvaults
304308 KEYVAULTS="${{ env.KEYVAULTS }}"
305- stripped=$(echo "$KEYVAULTS" | sed 's/\[\|\]//g')
306- IFS=',' read -r -a kvs <<< "$stripped"
307- for raw in "${kvs[@]}"; do
308- kv=$(echo "$raw" | sed 's/\"//g' | xargs)
309- [ -z "$kv" ] && continue
310- echo "Checking soft-delete state for KeyVault: $kv"
311- deleted=$(az keyvault list-deleted --query "[?name=='$kv']" -o json --subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }} || echo '[]')
312- if [ "$(echo "$deleted" | jq length)" -gt 0 ]; then
313- echo "Purging KeyVault $kv ..."
314- az keyvault purge --name "$kv" --no-wait || echo "Failed to purge $kv (non-fatal)"
309+
310+ # Remove the surrounding square brackets, if they exist
311+ stripped_keyvaults=$(echo "$KEYVAULTS" | sed 's/\[\|\]//g')
312+
313+ # Convert the comma-separated string into an array
314+ IFS=',' read -r -a resources_to_check <<< "$stripped_keyvaults"
315+
316+ # Append new resources to the array
317+ resources_to_check+=("${{ env.LOG_ANALYTICS_WORKSPACE_NAME }}" "${{ env.OPENAI_RESOURCE_NAME }}")
318+
319+ echo "List of resources to check: ${resources_to_check[@]}"
320+
321+ # Maximum number of retries
322+ max_retries=3
323+
324+ # Retry intervals in seconds (30, 60, 120)
325+ retry_intervals=(30 60 120)
326+
327+ # Retry mechanism to check resources
328+ retries=0
329+ while true; do
330+ resource_found=false
331+
332+ # Get the list of resources in YAML format again on each retry
333+ resource_list=$(az resource list --resource-group ${{ env.RESOURCE_GROUP_NAME }} --output yaml)
334+
335+ # Iterate through the resources to check
336+ for resource in "${resources_to_check[@]}"; do
337+ echo "Checking resource: $resource"
338+ if echo "$resource_list" | grep -q "name: $resource"; then
339+ echo "Resource '$resource' exists in the resource group."
340+ resource_found=true
341+ else
342+ echo "Resource '$resource' does not exist in the resource group."
343+ fi
344+ done
345+
346+ # If any resource exists, retry
347+ if [ "$resource_found" = true ]; then
348+ retries=$((retries + 1))
349+ if [ "$retries" -gt "$max_retries" ]; then
350+ echo "Maximum retry attempts reached. Exiting."
351+ break
352+ else
353+ # Wait for the appropriate interval for the current retry
354+ echo "Waiting for ${retry_intervals[$retries-1]} seconds before retrying..."
355+ sleep ${retry_intervals[$retries-1]}
356+ fi
315357 else
316- echo "KeyVault $kv not soft-deleted; skipping."
358+ echo "No resources found. Exiting."
359+ break
317360 fi
318361 done
319-
320- - name : Purge Log Analytics Workspace (best-effort)
362+
363+ - name : Purging the Resources
321364 if : always()
322365 run : |
366+
323367 set -e
324- if [ -n "${{ env.LOG_ANALYTICS_WORKSPACE_NAME }}" ]; then
325- echo "Attempting to purge Log Analytics workspace: ${{ env.LOG_ANALYTICS_WORKSPACE_NAME }} (best-effort)"
326- az monitor log-analytics workspace delete --force --workspace-name ${{ env.LOG_ANALYTICS_WORKSPACE_NAME }} --resource-group ${{ env.RESOURCE_GROUP_NAME }} --yes --verbose || echo "Workspace purge not applicable or already gone."
368+
369+ echo "Azure OpenAI: ${{ env.OPENAI_RESOURCE_NAME }}"
370+
371+ # Purge OpenAI Resource
372+ echo "Purging the OpenAI Resource..."
373+ if ! az resource delete --ids /subscriptions/${{ secrets.AZURE_SUBSCRIPTION_ID }}/providers/Microsoft.CognitiveServices/locations/northcentralus/resourceGroups/${{ env.RESOURCE_GROUP_NAME }}/deletedAccounts/${{ env.OPENAI_RESOURCE_NAME }} --verbose; then
374+ echo "Failed to purge openai resource: ${{ env.OPENAI_RESOURCE_NAME }}"
327375 else
328- echo "No Log Analytics workspace recorded. "
376+ echo "Purged the openai resource: ${{ env.OPENAI_RESOURCE_NAME }} "
329377 fi
330378
379+ # List of keyvaults
380+ KEYVAULTS="${{ env.KEYVAULTS }}"
381+
382+ # Remove the surrounding square brackets, if they exist
383+ stripped_keyvaults=$(echo "$KEYVAULTS" | sed 's/\[\|\]//g')
384+
385+ # Convert the comma-separated string into an array
386+ IFS=',' read -r -a keyvault_array <<< "$stripped_keyvaults"
387+
388+ echo "Using KeyVaults Array..."
389+ for keyvault_name in "${keyvault_array[@]}"; do
390+ echo "Processing KeyVault: $keyvault_name"
391+ # Check if the KeyVault is soft-deleted
392+ deleted_vaults=$(az keyvault list-deleted --query "[?name=='$keyvault_name']" -o json --subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }})
393+
394+ # If the KeyVault is found in the soft-deleted state, purge it
395+ if [ "$(echo "$deleted_vaults" | jq length)" -gt 0 ]; then
396+ echo "KeyVault '$keyvault_name' is soft-deleted. Proceeding to purge..."
397+ # Purge the KeyVault
398+ if az keyvault purge --name "$keyvault_name" --no-wait; then
399+ echo "Successfully purged KeyVault '$keyvault_name'."
400+ else
401+ echo "Failed to purge KeyVault '$keyvault_name'."
402+ fi
403+ else
404+ echo "KeyVault '$keyvault_name' is not soft-deleted. No action taken."
405+ fi
406+ done
407+ echo "Resource purging completed successfully"
408+
331409 - name : Send Notification on Failure
332410 if : failure() || needs.deploy.result == 'failure'
333411 run : |
@@ -344,11 +422,6 @@ jobs:
344422 -H "Content-Type: application/json" \
345423 -d "$EMAIL_BODY" || echo "Failed to send notification"
346424
347- - name : Final Resource Group Deletion
348- if : always()
349- run : |
350- echo "Final RG delete step retained for idempotency; primary deletion already initiated."
351-
352425 - name : Logout from Azure
353426 if : always()
354427 run : |
0 commit comments