diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4cb3d8139..a121f35c5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -7,4 +7,4 @@ # Specific directory ownership /ClientAdvisor/ @Avijit-Microsoft @Roopan-Microsoft @Prajwal-Microsoft -/ResearchAssistant/ @Avijit-Microsoft @Roopan-Microsoft @Prajwal-Microsoft \ No newline at end of file +/ResearchAssistant/ @Avijit-Microsoft @Roopan-Microsoft @Prajwal-Microsoft diff --git a/.github/workflows/CAdeploy.yml b/.github/workflows/CAdeploy.yml index 79ccef201..273dc8355 100644 --- a/.github/workflows/CAdeploy.yml +++ b/.github/workflows/CAdeploy.yml @@ -73,6 +73,39 @@ jobs: --template-file ClientAdvisor/Deployment/bicep/main.bicep \ --parameters solutionPrefix=${{ env.SOLUTION_PREFIX }} cosmosLocation=eastus2 + - name: List KeyVaults and Store in Array + id: list_keyvaults + run: | + + set -e + echo "Listing all KeyVaults in the resource group ${RESOURCE_GROUP_NAME}..." + + # Get the list of KeyVaults in the specified resource group + keyvaults=$(az resource list --resource-group ${{ env.RESOURCE_GROUP_NAME }} --query "[?type=='Microsoft.KeyVault/vaults'].name" -o tsv) + + if [ -z "$keyvaults" ]; then + echo "No KeyVaults found in resource group ${RESOURCE_GROUP_NAME}." + echo "KEYVAULTS=[]" >> $GITHUB_ENV # If no KeyVaults found, set an empty array + else + echo "KeyVaults found: $keyvaults" + + # Format the list into an array with proper formatting (no trailing comma) + keyvault_array="[" + first=true + for kv in $keyvaults; do + if [ "$first" = true ]; then + keyvault_array="$keyvault_array\"$kv\"" + first=false + else + keyvault_array="$keyvault_array,\"$kv\"" + fi + done + keyvault_array="$keyvault_array]" + + # Output the formatted array and save it to the environment variable + echo "KEYVAULTS=$keyvault_array" >> $GITHUB_ENV + fi + - name: Update PowerBI URL if: success() run: | @@ -109,10 +142,126 @@ jobs: else echo "Resource group does not exists." fi + + - name: Wait for resource deletion to complete + run: | + + # List of keyvaults + KEYVAULTS="${{ env.KEYVAULTS }}" + + # Remove the surrounding square brackets, if they exist + stripped_keyvaults=$(echo "$KEYVAULTS" | sed 's/\[\|\]//g') + + # Convert the comma-separated string into an array + IFS=',' read -r -a resources_to_check <<< "$stripped_keyvaults" + + # Append new resources to the array + resources_to_check+=("${{ env.SOLUTION_PREFIX }}-openai" "${{ env.SOLUTION_PREFIX }}-cogser") + + echo "List of resources to check: ${resources_to_check[@]}" + + # Get the list of resources in YAML format + resource_list=$(az resource list --resource-group ${{ env.RESOURCE_GROUP_NAME }} --output yaml) + + # Maximum number of retries + max_retries=3 + + # Retry intervals in seconds (30, 60, 120) + retry_intervals=(30 60 120) + + # Retry mechanism to check resources + retries=0 + while true; do + resource_found=false + + # Iterate through the resources to check + for resource in "${resources_to_check[@]}"; do + echo "Checking resource: $resource" + if echo "$resource_list" | grep -q "name: $resource"; then + echo "Resource '$resource' exists in the resource group." + resource_found=true + else + echo "Resource '$resource' does not exist in the resource group." + fi + done + + # If any resource exists, retry + if [ "$resource_found" = true ]; then + retries=$((retries + 1)) + if [ "$retries" -ge "$max_retries" ]; then + echo "Maximum retry attempts reached. Exiting." + break + else + # Wait for the appropriate interval for the current retry + echo "Waiting for ${retry_intervals[$retries-1]} seconds before retrying..." + sleep ${retry_intervals[$retries-1]} + fi + else + echo "No resources found. Exiting." + break + fi + done + + - name: Purging the Resources + if: success() + run: | + + set -e + # Define variables + OPENAI_COMMON_PART="-openai" + openai_name="${{ env.SOLUTION_PREFIX }}${OPENAI_COMMON_PART}" + echo "Azure OpenAI: $openai_name" + + MULTISERVICE_COMMON_PART="-cogser" + multiservice_account_name="${{ env.SOLUTION_PREFIX }}${MULTISERVICE_COMMON_PART}" + echo "Azure MultiService Account: $multiservice_account_name" + + # Purge OpenAI Resource + echo "Purging the OpenAI Resource..." + if ! az resource delete --ids /subscriptions/${{ secrets.AZURE_SUBSCRIPTION_ID }}/providers/Microsoft.CognitiveServices/locations/uksouth/resourceGroups/${{ env.RESOURCE_GROUP_NAME }}/deletedAccounts/$openai_name --verbose; then + echo "Failed to purge openai resource: $openai_name" + else + echo "Purged the openai resource: $openai_name" + fi + + # Purge MultiService Account Resource + echo "Purging the MultiService Account Resource..." + if ! az resource delete --ids /subscriptions/${{ secrets.AZURE_SUBSCRIPTION_ID }}/providers/Microsoft.CognitiveServices/locations/uksouth/resourceGroups/${{ env.RESOURCE_GROUP_NAME }}/deletedAccounts/$multiservice_account_name --verbose; then + echo "Failed to purge multiService account resource: $multiservice_account_name" + else + echo "Purged the multiService account resource: $multiservice_account_name" + fi + + # Ensure KEYVAULTS is properly formatted as a comma-separated string + KEYVAULTS="${{ env.KEYVAULTS }}" + + # Remove the surrounding square brackets, if they exist + stripped_keyvaults=$(echo "$KEYVAULTS" | sed 's/\[\|\]//g') + + # Convert the comma-separated string into an array + IFS=',' read -r -a keyvault_array <<< "$stripped_keyvaults" + + echo "Using KeyVaults Array..." + for keyvault_name in "${keyvault_array[@]}"; do + echo "Processing KeyVault: $keyvault_name" + # Check if the KeyVault is soft-deleted + deleted_vaults=$(az keyvault list-deleted --query "[?name=='$keyvault_name']" -o json --subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }}) + + # If the KeyVault is found in the soft-deleted state, purge it + if [ "$(echo "$deleted_vaults" | jq length)" -gt 0 ]; then + echo "KeyVault '$keyvault_name' is soft-deleted. Proceeding to purge..." + az keyvault purge --name "$keyvault_name" --no-wait + else + echo "KeyVault '$keyvault_name' is not soft-deleted. No action taken." + fi + done + + echo "Resource purging completed successfully" - name: Send Notification on Failure if: failure() run: | + RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" # Construct the email body @@ -127,4 +276,3 @@ jobs: curl -X POST "${{ secrets.LOGIC_APP_URL }}" \ -H "Content-Type: application/json" \ -d "$EMAIL_BODY" || echo "Failed to send notification" - diff --git a/.github/workflows/RAdeploy.yml b/.github/workflows/RAdeploy.yml index 571d0f47f..d70ac3688 100644 --- a/.github/workflows/RAdeploy.yml +++ b/.github/workflows/RAdeploy.yml @@ -70,7 +70,40 @@ jobs: --resource-group ${{ env.RESOURCE_GROUP_NAME }} \ --template-file ResearchAssistant/Deployment/bicep/main.bicep \ --parameters solutionPrefix=${{ env.SOLUTION_PREFIX }} - + + - name: List KeyVaults and Store in Array + id: list_keyvaults + run: | + + set -e + echo "Listing all KeyVaults in the resource group ${RESOURCE_GROUP_NAME}..." + + # Get the list of KeyVaults in the specified resource group + keyvaults=$(az resource list --resource-group ${{ env.RESOURCE_GROUP_NAME }} --query "[?type=='Microsoft.KeyVault/vaults'].name" -o tsv) + + if [ -z "$keyvaults" ]; then + echo "No KeyVaults found in resource group ${RESOURCE_GROUP_NAME}." + echo "KEYVAULTS=[]" >> $GITHUB_ENV # If no KeyVaults found, set an empty array + else + echo "KeyVaults found: $keyvaults" + + # Format the list into an array with proper formatting (no trailing comma) + keyvault_array="[" + first=true + for kv in $keyvaults; do + if [ "$first" = true ]; then + keyvault_array="$keyvault_array\"$kv\"" + first=false + else + keyvault_array="$keyvault_array,\"$kv\"" + fi + done + keyvault_array="$keyvault_array]" + + # Output the formatted array and save it to the environment variable + echo "KEYVAULTS=$keyvault_array" >> $GITHUB_ENV + fi + - name: Delete Bicep Deployment if: success() run: | @@ -88,6 +121,121 @@ jobs: echo "Resource group does not exists." fi + - name: Wait for resource deletion to complete + run: | + + # List of keyvaults + KEYVAULTS="${{ env.KEYVAULTS }}" + + # Remove the surrounding square brackets, if they exist + stripped_keyvaults=$(echo "$KEYVAULTS" | sed 's/\[\|\]//g') + + # Convert the comma-separated string into an array + IFS=',' read -r -a resources_to_check <<< "$stripped_keyvaults" + + # Append new resources to the array + resources_to_check+=("${{ env.SOLUTION_PREFIX }}-openai" "${{ env.SOLUTION_PREFIX }}-cogser") + + echo "List of resources to check: ${resources_to_check[@]}" + + # Get the list of resources in YAML format + resource_list=$(az resource list --resource-group ${{ env.RESOURCE_GROUP_NAME }} --output yaml) + + # Maximum number of retries + max_retries=3 + + # Retry intervals in seconds (30, 60, 120) + retry_intervals=(30 60 120) + + # Retry mechanism to check resources + retries=0 + while true; do + resource_found=false + + # Iterate through the resources to check + for resource in "${resources_to_check[@]}"; do + echo "Checking resource: $resource" + if echo "$resource_list" | grep -q "name: $resource"; then + echo "Resource '$resource' exists in the resource group." + resource_found=true + else + echo "Resource '$resource' does not exist in the resource group." + fi + done + + # If any resource exists, retry + if [ "$resource_found" = true ]; then + retries=$((retries + 1)) + if [ "$retries" -ge "$max_retries" ]; then + echo "Maximum retry attempts reached. Exiting." + break + else + # Wait for the appropriate interval for the current retry + echo "Waiting for ${retry_intervals[$retries-1]} seconds before retrying..." + sleep ${retry_intervals[$retries-1]} + fi + else + echo "No resources found. Exiting." + break + fi + done + + - name: Purging the Resources + if: success() + run: | + + set -e + # Define variables + OPENAI_COMMON_PART="-openai" + openai_name="${{ env.SOLUTION_PREFIX }}${OPENAI_COMMON_PART}" + echo "Azure OpenAI: $openai_name" + + MULTISERVICE_COMMON_PART="-cogser" + multiservice_account_name="${{ env.SOLUTION_PREFIX }}${MULTISERVICE_COMMON_PART}" + echo "Azure MultiService Account: $multiservice_account_name" + + # Purge OpenAI Resource + echo "Purging the OpenAI Resource..." + if ! az resource delete --ids /subscriptions/${{ secrets.AZURE_SUBSCRIPTION_ID }}/providers/Microsoft.CognitiveServices/locations/eastus2/resourceGroups/${{ env.RESOURCE_GROUP_NAME }}/deletedAccounts/$openai_name --verbose; then + echo "Failed to purge openai resource: $openai_name" + else + echo "Purged the openai resource: $openai_name" + fi + + # Purge MultiService Account Resource + echo "Purging the MultiService Account Resource..." + if ! az resource delete --ids /subscriptions/${{ secrets.AZURE_SUBSCRIPTION_ID }}/providers/Microsoft.CognitiveServices/locations/eastus2/resourceGroups/${{ env.RESOURCE_GROUP_NAME }}/deletedAccounts/$multiservice_account_name --verbose; then + echo "Failed to purge multiService account resource: $multiservice_account_name" + else + echo "Purged the multiService account resource: $multiservice_account_name" + fi + + # Ensure KEYVAULTS is properly formatted as a comma-separated string + KEYVAULTS="${{ env.KEYVAULTS }}" + + # Remove the surrounding square brackets, if they exist + stripped_keyvaults=$(echo "$KEYVAULTS" | sed 's/\[\|\]//g') + + # Convert the comma-separated string into an array + IFS=',' read -r -a keyvault_array <<< "$stripped_keyvaults" + + echo "Using KeyVaults Array..." + for keyvault_name in "${keyvault_array[@]}"; do + echo "Processing KeyVault: $keyvault_name" + # Check if the KeyVault is soft-deleted + deleted_vaults=$(az keyvault list-deleted --query "[?name=='$keyvault_name']" -o json --subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }}) + + # If the KeyVault is found in the soft-deleted state, purge it + if [ "$(echo "$deleted_vaults" | jq length)" -gt 0 ]; then + echo "KeyVault '$keyvault_name' is soft-deleted. Proceeding to purge..." + az keyvault purge --name "$keyvault_name" --no-wait + else + echo "KeyVault '$keyvault_name' is not soft-deleted. No action taken." + fi + done + + echo "Resource purging completed successfully" + - name: Send Notification on Failure if: failure() run: |