From fd6512c3c28c15fff1476023661c6361187dbeb3 Mon Sep 17 00:00:00 2001
From: Harmanpreet-Microsoft
Date: Fri, 2 Jan 2026 15:43:26 +0530
Subject: [PATCH 01/38] Change Docker tag name from 'latest_waf' to 'latest'
---
.github/workflows/docker-build.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml
index 75ea8fe7..5d72682d 100644
--- a/.github/workflows/docker-build.yml
+++ b/.github/workflows/docker-build.yml
@@ -49,7 +49,7 @@ jobs:
id: determine_tag
run: |
if [[ "${{ github.ref_name }}" == "main" ]]; then
- echo "tagname=latest_waf" >> $GITHUB_OUTPUT
+ echo "tagname=latest" >> $GITHUB_OUTPUT
elif [[ "${{ github.ref_name }}" == "dev" ]]; then
echo "tagname=dev" >> $GITHUB_OUTPUT
elif [[ "${{ github.ref_name }}" == "demo" ]]; then
From 89ad24bae9c437ad8b72c6b8c2c9152ad04c1bd2 Mon Sep 17 00:00:00 2001
From: Harmanpreet-Microsoft
Date: Fri, 2 Jan 2026 15:47:33 +0530
Subject: [PATCH 02/38] Add name to create-release workflow
---
.github/workflows/create-release.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml
index 1936e019..cf3da760 100644
--- a/.github/workflows/create-release.yml
+++ b/.github/workflows/create-release.yml
@@ -1,3 +1,4 @@
+name: Create-Release
on:
push:
branches:
@@ -7,7 +8,6 @@ permissions:
contents: write
pull-requests: write
-name: Create-Release
jobs:
create-release:
From 36e8d26213dd0c8bd4d82c42268a981bf31657f1 Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Tue, 27 Jan 2026 12:10:00 +0530
Subject: [PATCH 03/38] Implement Test Automation Workflow v2 and Refactor
Agent Scripts
- Added a new GitHub Actions workflow for test automation (test-automation-v2.yml) to modernize code testing with enhanced reporting and retry mechanisms.
- Refactored agent creation scripts (run_create_agents_scripts.sh) to remove public access management functions and streamline Azure role assignments for service principals.
- Updated data upload scripts (run_upload_data_scripts.sh) to eliminate public access management and improve role assignment logic for Cosmos DB and AI Search Service.
---
.github/workflows/deploy-linux.yml | 267 ++++++++++
.github/workflows/deploy-orchestrator.yml | 142 +++++
.github/workflows/deploy-windows.yml | 263 +++++++++
.github/workflows/deploy.yml | 2 +-
.github/workflows/job-cleanup-deployment.yml | 111 ++++
.github/workflows/job-deploy-linux.yml | 363 +++++++++++++
.github/workflows/job-deploy-windows.yml | 370 +++++++++++++
.github/workflows/job-deploy.yml | 497 ++++++++++++++++++
.github/workflows/job-docker-build.yml | 135 +++++
.github/workflows/job-send-notification.yml | 431 +++++++++++++++
.github/workflows/test-automation-v2.yml | 196 +++++++
.../run_create_agents_scripts.sh | 191 ++-----
.../data_scripts/run_upload_data_scripts.sh | 372 ++++---------
13 files changed, 2947 insertions(+), 393 deletions(-)
create mode 100644 .github/workflows/deploy-linux.yml
create mode 100644 .github/workflows/deploy-orchestrator.yml
create mode 100644 .github/workflows/deploy-windows.yml
create mode 100644 .github/workflows/job-cleanup-deployment.yml
create mode 100644 .github/workflows/job-deploy-linux.yml
create mode 100644 .github/workflows/job-deploy-windows.yml
create mode 100644 .github/workflows/job-deploy.yml
create mode 100644 .github/workflows/job-docker-build.yml
create mode 100644 .github/workflows/job-send-notification.yml
create mode 100644 .github/workflows/test-automation-v2.yml
diff --git a/.github/workflows/deploy-linux.yml b/.github/workflows/deploy-linux.yml
new file mode 100644
index 00000000..683f2f2c
--- /dev/null
+++ b/.github/workflows/deploy-linux.yml
@@ -0,0 +1,267 @@
+name: Deploy-Test-Cleanup (v2) Linux
+permissions:
+ contents: read
+ actions: read
+on:
+ workflow_run:
+ workflows: ["Build Docker and Optional Push"]
+ types:
+ - completed
+ branches:
+ - main
+ - dev
+ - demo
+ workflow_dispatch:
+ inputs:
+ azure_location:
+ description: 'Azure Location For Deployment'
+ required: false
+ default: 'australiaeast'
+ type: choice
+ options:
+ - 'australiaeast'
+ - 'centralus'
+ - 'eastasia'
+ - 'eastus2'
+ - 'japaneast'
+ - 'northeurope'
+ - 'southeastasia'
+ - 'uksouth'
+ resource_group_name:
+ description: 'Resource Group Name (Optional)'
+ required: false
+ default: ''
+ type: string
+ waf_enabled:
+ description: 'Enable WAF'
+ required: false
+ default: false
+ type: boolean
+ EXP:
+ description: 'Enable EXP'
+ required: false
+ default: false
+ type: boolean
+ build_docker_image:
+ description: 'Build & Push Docker Image (Optional)'
+ required: false
+ default: false
+ type: boolean
+ cleanup_resources:
+ description: 'Cleanup Deployed Resources'
+ required: false
+ default: false
+ type: boolean
+ run_e2e_tests:
+ description: 'Run End-to-End Tests'
+ required: false
+ default: 'GoldenPath-Testing'
+ type: choice
+ options:
+ - 'GoldenPath-Testing'
+ - 'Smoke-Testing'
+ - 'None'
+ AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID:
+ description: 'Log Analytics Workspace ID (Optional)'
+ required: false
+ default: ''
+ type: string
+ AZURE_EXISTING_AI_PROJECT_RESOURCE_ID:
+ description: 'AI Project Resource ID (Optional)'
+ required: false
+ default: ''
+ type: string
+ existing_webapp_url:
+ description: 'Existing Container WebApp URL (Skips Deployment)'
+ required: false
+ default: ''
+ type: string
+
+ schedule:
+ - cron: '0 5,17 * * *' # Runs at 5:00 AM and 5:00 PM GMT
+
+jobs:
+ validate-inputs:
+ runs-on: ubuntu-latest
+ outputs:
+ validation_passed: ${{ steps.validate.outputs.passed }}
+ azure_location: ${{ steps.validate.outputs.azure_location }}
+ resource_group_name: ${{ steps.validate.outputs.resource_group_name }}
+ waf_enabled: ${{ steps.validate.outputs.waf_enabled }}
+ exp: ${{ steps.validate.outputs.exp }}
+ build_docker_image: ${{ steps.validate.outputs.build_docker_image }}
+ cleanup_resources: ${{ steps.validate.outputs.cleanup_resources }}
+ run_e2e_tests: ${{ steps.validate.outputs.run_e2e_tests }}
+ azure_env_log_analytics_workspace_id: ${{ steps.validate.outputs.azure_env_log_analytics_workspace_id }}
+ azure_existing_ai_project_resource_id: ${{ steps.validate.outputs.azure_existing_ai_project_resource_id }}
+ existing_webapp_url: ${{ steps.validate.outputs.existing_webapp_url }}
+ steps:
+ - name: Validate Workflow Input Parameters
+ id: validate
+ shell: bash
+ env:
+ INPUT_AZURE_LOCATION: ${{ github.event.inputs.azure_location }}
+ INPUT_RESOURCE_GROUP_NAME: ${{ github.event.inputs.resource_group_name }}
+ INPUT_WAF_ENABLED: ${{ github.event.inputs.waf_enabled }}
+ INPUT_EXP: ${{ github.event.inputs.EXP }}
+ INPUT_BUILD_DOCKER_IMAGE: ${{ github.event.inputs.build_docker_image }}
+ INPUT_CLEANUP_RESOURCES: ${{ github.event.inputs.cleanup_resources }}
+ INPUT_RUN_E2E_TESTS: ${{ github.event.inputs.run_e2e_tests }}
+ INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ github.event.inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}
+ INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ github.event.inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
+ INPUT_EXISTING_WEBAPP_URL: ${{ github.event.inputs.existing_webapp_url }}
+ run: |
+ echo "๐ Validating workflow input parameters..."
+ VALIDATION_FAILED=false
+
+ # Validate azure_location (Azure region format)
+ LOCATION="${INPUT_AZURE_LOCATION:-australiaeast}"
+
+ if [[ ! "$LOCATION" =~ ^[a-z0-9]+$ ]]; then
+ echo "โ ERROR: azure_location '$LOCATION' is invalid. Must contain only lowercase letters and numbers"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
azure_location: '$LOCATION' is valid"
+ fi
+
+ # Validate resource_group_name (Azure naming convention, optional)
+ if [[ -n "$INPUT_RESOURCE_GROUP_NAME" ]]; then
+ if [[ ! "$INPUT_RESOURCE_GROUP_NAME" =~ ^[a-zA-Z0-9._\(\)-]+$ ]] || [[ "$INPUT_RESOURCE_GROUP_NAME" =~ \.$ ]]; then
+ echo "โ ERROR: resource_group_name '$INPUT_RESOURCE_GROUP_NAME' is invalid. Must contain only alphanumerics, periods, underscores, hyphens, and parentheses. Cannot end with period."
+ VALIDATION_FAILED=true
+ elif [[ ${#INPUT_RESOURCE_GROUP_NAME} -gt 90 ]]; then
+ echo "โ ERROR: resource_group_name '$INPUT_RESOURCE_GROUP_NAME' exceeds 90 characters (length: ${#INPUT_RESOURCE_GROUP_NAME})"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
resource_group_name: '$INPUT_RESOURCE_GROUP_NAME' is valid"
+ fi
+ else
+ echo "โ
resource_group_name: Not provided (will be auto-generated)"
+ fi
+
+ # Validate waf_enabled (boolean)
+ WAF_ENABLED="${INPUT_WAF_ENABLED:-false}"
+ if [[ "$WAF_ENABLED" != "true" && "$WAF_ENABLED" != "false" ]]; then
+ echo "โ ERROR: waf_enabled must be 'true' or 'false', got: '$WAF_ENABLED'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
waf_enabled: '$WAF_ENABLED' is valid"
+ fi
+
+ # Validate EXP (boolean)
+ EXP_ENABLED="${INPUT_EXP:-false}"
+ if [[ "$EXP_ENABLED" != "true" && "$EXP_ENABLED" != "false" ]]; then
+ echo "โ ERROR: EXP must be 'true' or 'false', got: '$EXP_ENABLED'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
EXP: '$EXP_ENABLED' is valid"
+ fi
+
+ # Validate build_docker_image (boolean)
+ BUILD_DOCKER="${INPUT_BUILD_DOCKER_IMAGE:-false}"
+ if [[ "$BUILD_DOCKER" != "true" && "$BUILD_DOCKER" != "false" ]]; then
+ echo "โ ERROR: build_docker_image must be 'true' or 'false', got: '$BUILD_DOCKER'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
build_docker_image: '$BUILD_DOCKER' is valid"
+ fi
+
+ # Validate cleanup_resources (boolean)
+ CLEANUP_RESOURCES="${INPUT_CLEANUP_RESOURCES:-false}"
+ if [[ "$CLEANUP_RESOURCES" != "true" && "$CLEANUP_RESOURCES" != "false" ]]; then
+ echo "โ ERROR: cleanup_resources must be 'true' or 'false', got: '$CLEANUP_RESOURCES'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
cleanup_resources: '$CLEANUP_RESOURCES' is valid"
+ fi
+
+ # Validate run_e2e_tests (specific allowed values)
+ TEST_OPTION="${INPUT_RUN_E2E_TESTS:-GoldenPath-Testing}"
+ if [[ "$TEST_OPTION" != "GoldenPath-Testing" && "$TEST_OPTION" != "Smoke-Testing" && "$TEST_OPTION" != "None" ]]; then
+ echo "โ ERROR: run_e2e_tests must be one of: GoldenPath-Testing, Smoke-Testing, None, got: '$TEST_OPTION'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
run_e2e_tests: '$TEST_OPTION' is valid"
+ fi
+
+ # Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (optional, Azure Resource ID format)
+ if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then
+ if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/microsoft\.operationalinsights/workspaces/[^/]+$ ]]; then
+ echo "โ ERROR: AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID is invalid. Must be a valid Azure Resource ID format:"
+ echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}"
+ echo " Got: '$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: Valid Resource ID format"
+ fi
+ else
+ echo "โ
AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: Not provided (optional)"
+ fi
+
+ # Validate AZURE_EXISTING_AI_PROJECT_RESOURCE_ID (optional, Azure Resource ID format)
+ if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
+ if [[ ! "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/(Microsoft\.MachineLearningServices/(workspaces|projects)/[^/]+|Microsoft\.CognitiveServices/accounts/[^/]+/projects/[^/]+)$ ]]; then
+ echo "โ ERROR: AZURE_EXISTING_AI_PROJECT_RESOURCE_ID is invalid. Must be a valid Azure Resource ID format:"
+ echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CognitiveServices/accounts/{accountName}/projects/{projectName}"
+ echo " Got: '$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: Valid Resource ID format"
+ fi
+ else
+ echo "โ
AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: Not provided (optional)"
+ fi
+
+ # Validate existing_webapp_url (optional, must start with https)
+ if [[ -n "$INPUT_EXISTING_WEBAPP_URL" ]]; then
+ if [[ ! "$INPUT_EXISTING_WEBAPP_URL" =~ ^https:// ]]; then
+ echo "โ ERROR: existing_webapp_url must start with 'https://', got: '$INPUT_EXISTING_WEBAPP_URL'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
existing_webapp_url: '$INPUT_EXISTING_WEBAPP_URL' is valid"
+ fi
+ else
+ echo "โ
existing_webapp_url: Not provided (will perform deployment)"
+ fi
+
+ # Fail workflow if any validation failed
+ if [[ "$VALIDATION_FAILED" == "true" ]]; then
+ echo ""
+ echo "โ Parameter validation failed. Please correct the errors above and try again."
+ exit 1
+ fi
+
+ echo ""
+ echo "โ
All input parameters validated successfully!"
+
+ # Output validated values
+ echo "passed=true" >> $GITHUB_OUTPUT
+ echo "azure_location=$LOCATION" >> $GITHUB_OUTPUT
+ echo "resource_group_name=$INPUT_RESOURCE_GROUP_NAME" >> $GITHUB_OUTPUT
+ echo "waf_enabled=$WAF_ENABLED" >> $GITHUB_OUTPUT
+ echo "exp=$EXP_ENABLED" >> $GITHUB_OUTPUT
+ echo "build_docker_image=$BUILD_DOCKER" >> $GITHUB_OUTPUT
+ echo "cleanup_resources=$CLEANUP_RESOURCES" >> $GITHUB_OUTPUT
+ echo "run_e2e_tests=$TEST_OPTION" >> $GITHUB_OUTPUT
+ echo "azure_env_log_analytics_workspace_id=$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" >> $GITHUB_OUTPUT
+ echo "azure_existing_ai_project_resource_id=$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" >> $GITHUB_OUTPUT
+ echo "existing_webapp_url=$INPUT_EXISTING_WEBAPP_URL" >> $GITHUB_OUTPUT
+
+ Run:
+ needs: validate-inputs
+ if: needs.validate-inputs.outputs.validation_passed == 'true'
+ uses: ./.github/workflows/deploy-orchestrator.yml
+ with:
+ runner_os: ubuntu-latest
+ azure_location: ${{ needs.validate-inputs.outputs.azure_location }}
+ resource_group_name: ${{ needs.validate-inputs.outputs.resource_group_name }}
+ waf_enabled: ${{ needs.validate-inputs.outputs.waf_enabled == 'true' }}
+ EXP: ${{ needs.validate-inputs.outputs.exp == 'true' }}
+ build_docker_image: ${{ needs.validate-inputs.outputs.build_docker_image == 'true' }}
+ cleanup_resources: ${{ needs.validate-inputs.outputs.cleanup_resources == 'true' }}
+ run_e2e_tests: ${{ needs.validate-inputs.outputs.run_e2e_tests }}
+ AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ needs.validate-inputs.outputs.azure_env_log_analytics_workspace_id }}
+ AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ needs.validate-inputs.outputs.azure_existing_ai_project_resource_id }}
+ existing_webapp_url: ${{ needs.validate-inputs.outputs.existing_webapp_url }}
+ trigger_type: ${{ github.event_name }}
+ secrets: inherit
diff --git a/.github/workflows/deploy-orchestrator.yml b/.github/workflows/deploy-orchestrator.yml
new file mode 100644
index 00000000..8e326abd
--- /dev/null
+++ b/.github/workflows/deploy-orchestrator.yml
@@ -0,0 +1,142 @@
+name: Deployment orchestrator
+
+permissions:
+ contents: read
+ actions: read
+
+on:
+ workflow_call:
+ inputs:
+ runner_os:
+ description: 'Runner OS (ubuntu-latest or windows-latest)'
+ required: true
+ type: string
+ azure_location:
+ description: 'Azure Location For Deployment'
+ required: false
+ default: 'australiaeast'
+ type: string
+ resource_group_name:
+ description: 'Resource Group Name (Optional)'
+ required: false
+ default: ''
+ type: string
+ waf_enabled:
+ description: 'Enable WAF'
+ required: false
+ default: false
+ type: boolean
+ EXP:
+ description: 'Enable EXP'
+ required: false
+ default: false
+ type: boolean
+ build_docker_image:
+ description: 'Build And Push Docker Image (Optional)'
+ required: false
+ default: false
+ type: boolean
+ cleanup_resources:
+ description: 'Cleanup Deployed Resources'
+ required: false
+ default: false
+ type: boolean
+ run_e2e_tests:
+ description: 'Run End-to-End Tests'
+ required: false
+ default: 'GoldenPath-Testing'
+ type: string
+ AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID:
+ description: 'Log Analytics Workspace ID (Optional)'
+ required: false
+ default: ''
+ type: string
+ AZURE_EXISTING_AI_PROJECT_RESOURCE_ID:
+ description: 'AI Project Resource ID (Optional)'
+ required: false
+ default: ''
+ type: string
+ existing_webapp_url:
+ description: 'Existing Container WebApp URL (Skips Deployment)'
+ required: false
+ default: ''
+ type: string
+ trigger_type:
+ description: 'Trigger type (workflow_dispatch, pull_request, schedule)'
+ required: true
+ type: string
+
+env:
+ AZURE_DEV_COLLECT_TELEMETRY: ${{ vars.AZURE_DEV_COLLECT_TELEMETRY }}
+
+jobs:
+ docker-build:
+ uses: ./.github/workflows/job-docker-build.yml
+ with:
+ trigger_type: ${{ inputs.trigger_type }}
+ build_docker_image: ${{ inputs.build_docker_image }}
+ secrets: inherit
+
+ deploy:
+ if: "!cancelled() && (needs.docker-build.result == 'success' || needs.docker-build.result == 'skipped') && (inputs.trigger_type != 'workflow_dispatch' || inputs.existing_webapp_url == '' || inputs.existing_webapp_url == null)"
+ needs: docker-build
+ uses: ./.github/workflows/job-deploy.yml
+ with:
+ trigger_type: ${{ inputs.trigger_type }}
+ runner_os: ${{ inputs.runner_os }}
+ azure_location: ${{ inputs.azure_location }}
+ resource_group_name: ${{ inputs.resource_group_name }}
+ waf_enabled: ${{ inputs.waf_enabled }}
+ EXP: ${{ inputs.EXP }}
+ build_docker_image: ${{ inputs.build_docker_image }}
+ existing_webapp_url: ${{ inputs.existing_webapp_url }}
+ AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}
+ AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
+ docker_image_tag: ${{ needs.docker-build.outputs.IMAGE_TAG }}
+ run_e2e_tests: ${{ inputs.run_e2e_tests }}
+ cleanup_resources: ${{ inputs.cleanup_resources }}
+ secrets: inherit
+
+ e2e-test:
+ if: "!cancelled() && ((needs.deploy.result == 'success' && needs.deploy.outputs.CONTAINER_WEB_APPURL != '') || (inputs.existing_webapp_url != '' && inputs.existing_webapp_url != null)) && (inputs.trigger_type != 'workflow_dispatch' || (inputs.run_e2e_tests != 'None' && inputs.run_e2e_tests != '' && inputs.run_e2e_tests != null))"
+ needs: [docker-build, deploy]
+ uses: ./.github/workflows/test-automation-v2.yml
+ with:
+ TEST_URL: ${{ needs.deploy.outputs.CONTAINER_WEB_APPURL || inputs.existing_webapp_url }}
+ TEST_SUITE: ${{ inputs.trigger_type == 'workflow_dispatch' && inputs.run_e2e_tests || 'GoldenPath-Testing' }}
+ secrets: inherit
+
+ send-notification:
+ if: "!cancelled()"
+ needs: [docker-build, deploy, e2e-test]
+ uses: ./.github/workflows/job-send-notification.yml
+ with:
+ trigger_type: ${{ inputs.trigger_type }}
+ waf_enabled: ${{ inputs.waf_enabled }}
+ EXP: ${{ inputs.EXP }}
+ run_e2e_tests: ${{ inputs.run_e2e_tests }}
+ existing_webapp_url: ${{ inputs.existing_webapp_url }}
+ deploy_result: ${{ needs.deploy.result }}
+ e2e_test_result: ${{ needs.e2e-test.result }}
+ CONTAINER_WEB_APPURL: ${{ needs.deploy.outputs.CONTAINER_WEB_APPURL }}
+ RESOURCE_GROUP_NAME: ${{ needs.deploy.outputs.RESOURCE_GROUP_NAME }}
+ QUOTA_FAILED: ${{ needs.deploy.outputs.QUOTA_FAILED }}
+ TEST_SUCCESS: ${{ needs.e2e-test.outputs.TEST_SUCCESS }}
+ TEST_REPORT_URL: ${{ needs.e2e-test.outputs.TEST_REPORT_URL }}
+ secrets: inherit
+
+ cleanup-deployment:
+ if: "!cancelled() && needs.deploy.result == 'success' && needs.deploy.outputs.RESOURCE_GROUP_NAME != '' && inputs.existing_webapp_url == '' && (inputs.trigger_type != 'workflow_dispatch' || inputs.cleanup_resources)"
+ needs: [docker-build, deploy, e2e-test]
+ uses: ./.github/workflows/job-cleanup-deployment.yml
+ with:
+ runner_os: ${{ inputs.runner_os }}
+ trigger_type: ${{ inputs.trigger_type }}
+ cleanup_resources: ${{ inputs.cleanup_resources }}
+ existing_webapp_url: ${{ inputs.existing_webapp_url }}
+ RESOURCE_GROUP_NAME: ${{ needs.deploy.outputs.RESOURCE_GROUP_NAME }}
+ AZURE_LOCATION: ${{ needs.deploy.outputs.AZURE_LOCATION }}
+ AZURE_ENV_OPENAI_LOCATION: ${{ needs.deploy.outputs.AZURE_ENV_OPENAI_LOCATION }}
+ ENV_NAME: ${{ needs.deploy.outputs.ENV_NAME }}
+ IMAGE_TAG: ${{ needs.deploy.outputs.IMAGE_TAG }}
+ secrets: inherit
diff --git a/.github/workflows/deploy-windows.yml b/.github/workflows/deploy-windows.yml
new file mode 100644
index 00000000..6e35c534
--- /dev/null
+++ b/.github/workflows/deploy-windows.yml
@@ -0,0 +1,263 @@
+name: Deploy-Test-Cleanup (v2) Windows
+permissions:
+ contents: read
+ actions: read
+on:
+ workflow_dispatch:
+ inputs:
+ azure_location:
+ description: 'Azure Location For Deployment'
+ required: false
+ default: 'australiaeast'
+ type: choice
+ options:
+ - 'australiaeast'
+ - 'centralus'
+ - 'eastasia'
+ - 'eastus2'
+ - 'japaneast'
+ - 'northeurope'
+ - 'southeastasia'
+ - 'uksouth'
+ resource_group_name:
+ description: 'Resource Group Name (Optional)'
+ required: false
+ default: ''
+ type: string
+
+ waf_enabled:
+ description: 'Enable WAF'
+ required: false
+ default: false
+ type: boolean
+ EXP:
+ description: 'Enable EXP'
+ required: false
+ default: false
+ type: boolean
+ build_docker_image:
+ description: 'Build & Push Docker Image (Optional)'
+ required: false
+ default: false
+ type: boolean
+
+ cleanup_resources:
+ description: 'Cleanup Deployed Resources'
+ required: false
+ default: false
+ type: boolean
+
+ run_e2e_tests:
+ description: 'Run End-to-End Tests'
+ required: false
+ default: 'GoldenPath-Testing'
+ type: choice
+ options:
+ - 'GoldenPath-Testing'
+ - 'Smoke-Testing'
+ - 'None'
+
+ AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID:
+ description: 'Log Analytics Workspace ID (Optional)'
+ required: false
+ default: ''
+ type: string
+ AZURE_EXISTING_AI_PROJECT_RESOURCE_ID:
+ description: 'AI Project Resource ID (Optional)'
+ required: false
+ default: ''
+ type: string
+ existing_webapp_url:
+ description: 'Existing Container WebApp URL (Skips Deployment)'
+ required: false
+ default: ''
+ type: string
+
+ # schedule:
+ # - cron: '0 5,17 * * *' # Runs at 5:00 AM and 5:00 PM GMT
+
+jobs:
+ validate-inputs:
+ runs-on: ubuntu-latest
+ outputs:
+ validation_passed: ${{ steps.validate.outputs.passed }}
+ azure_location: ${{ steps.validate.outputs.azure_location }}
+ resource_group_name: ${{ steps.validate.outputs.resource_group_name }}
+ waf_enabled: ${{ steps.validate.outputs.waf_enabled }}
+ exp: ${{ steps.validate.outputs.exp }}
+ build_docker_image: ${{ steps.validate.outputs.build_docker_image }}
+ cleanup_resources: ${{ steps.validate.outputs.cleanup_resources }}
+ run_e2e_tests: ${{ steps.validate.outputs.run_e2e_tests }}
+ azure_env_log_analytics_workspace_id: ${{ steps.validate.outputs.azure_env_log_analytics_workspace_id }}
+ azure_existing_ai_project_resource_id: ${{ steps.validate.outputs.azure_existing_ai_project_resource_id }}
+ existing_webapp_url: ${{ steps.validate.outputs.existing_webapp_url }}
+ steps:
+ - name: Validate Workflow Input Parameters
+ id: validate
+ shell: bash
+ env:
+ INPUT_AZURE_LOCATION: ${{ github.event.inputs.azure_location }}
+ INPUT_RESOURCE_GROUP_NAME: ${{ github.event.inputs.resource_group_name }}
+ INPUT_WAF_ENABLED: ${{ github.event.inputs.waf_enabled }}
+ INPUT_EXP: ${{ github.event.inputs.EXP }}
+ INPUT_BUILD_DOCKER_IMAGE: ${{ github.event.inputs.build_docker_image }}
+ INPUT_CLEANUP_RESOURCES: ${{ github.event.inputs.cleanup_resources }}
+ INPUT_RUN_E2E_TESTS: ${{ github.event.inputs.run_e2e_tests }}
+ INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ github.event.inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}
+ INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ github.event.inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
+ INPUT_EXISTING_WEBAPP_URL: ${{ github.event.inputs.existing_webapp_url }}
+ run: |
+ echo "๐ Validating workflow input parameters..."
+ VALIDATION_FAILED=false
+
+ # Validate azure_location (Azure region format)
+ LOCATION="${INPUT_AZURE_LOCATION:-australiaeast}"
+
+ if [[ ! "$LOCATION" =~ ^[a-z0-9]+$ ]]; then
+ echo "โ ERROR: azure_location '$LOCATION' is invalid. Must contain only lowercase letters and numbers"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
azure_location: '$LOCATION' is valid"
+ fi
+
+ # Validate resource_group_name (Azure naming convention, optional)
+ if [[ -n "$INPUT_RESOURCE_GROUP_NAME" ]]; then
+ if [[ ! "$INPUT_RESOURCE_GROUP_NAME" =~ ^[a-zA-Z0-9._\(\)-]+$ ]] || [[ "$INPUT_RESOURCE_GROUP_NAME" =~ \.$ ]]; then
+ echo "โ ERROR: resource_group_name '$INPUT_RESOURCE_GROUP_NAME' is invalid. Must contain only alphanumerics, periods, underscores, hyphens, and parentheses. Cannot end with period."
+ VALIDATION_FAILED=true
+ elif [[ ${#INPUT_RESOURCE_GROUP_NAME} -gt 90 ]]; then
+ echo "โ ERROR: resource_group_name '$INPUT_RESOURCE_GROUP_NAME' exceeds 90 characters (length: ${#INPUT_RESOURCE_GROUP_NAME})"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
resource_group_name: '$INPUT_RESOURCE_GROUP_NAME' is valid"
+ fi
+ else
+ echo "โ
resource_group_name: Not provided (will be auto-generated)"
+ fi
+
+ # Validate waf_enabled (boolean)
+ WAF_ENABLED="${INPUT_WAF_ENABLED:-false}"
+ if [[ "$WAF_ENABLED" != "true" && "$WAF_ENABLED" != "false" ]]; then
+ echo "โ ERROR: waf_enabled must be 'true' or 'false', got: '$WAF_ENABLED'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
waf_enabled: '$WAF_ENABLED' is valid"
+ fi
+
+ # Validate EXP (boolean)
+ EXP_ENABLED="${INPUT_EXP:-false}"
+ if [[ "$EXP_ENABLED" != "true" && "$EXP_ENABLED" != "false" ]]; then
+ echo "โ ERROR: EXP must be 'true' or 'false', got: '$EXP_ENABLED'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
EXP: '$EXP_ENABLED' is valid"
+ fi
+
+ # Validate build_docker_image (boolean)
+ BUILD_DOCKER="${INPUT_BUILD_DOCKER_IMAGE:-false}"
+ if [[ "$BUILD_DOCKER" != "true" && "$BUILD_DOCKER" != "false" ]]; then
+ echo "โ ERROR: build_docker_image must be 'true' or 'false', got: '$BUILD_DOCKER'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
build_docker_image: '$BUILD_DOCKER' is valid"
+ fi
+
+ # Validate cleanup_resources (boolean)
+ CLEANUP_RESOURCES="${INPUT_CLEANUP_RESOURCES:-false}"
+ if [[ "$CLEANUP_RESOURCES" != "true" && "$CLEANUP_RESOURCES" != "false" ]]; then
+ echo "โ ERROR: cleanup_resources must be 'true' or 'false', got: '$CLEANUP_RESOURCES'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
cleanup_resources: '$CLEANUP_RESOURCES' is valid"
+ fi
+
+ # Validate run_e2e_tests (specific allowed values)
+ TEST_OPTION="${INPUT_RUN_E2E_TESTS:-GoldenPath-Testing}"
+ if [[ "$TEST_OPTION" != "GoldenPath-Testing" && "$TEST_OPTION" != "Smoke-Testing" && "$TEST_OPTION" != "None" ]]; then
+ echo "โ ERROR: run_e2e_tests must be one of: GoldenPath-Testing, Smoke-Testing, None, got: '$TEST_OPTION'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
run_e2e_tests: '$TEST_OPTION' is valid"
+ fi
+
+ # Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (optional, Azure Resource ID format)
+ if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then
+ if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/microsoft\.operationalinsights/workspaces/[^/]+$ ]]; then
+ echo "โ ERROR: AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID is invalid. Must be a valid Azure Resource ID format:"
+ echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}"
+ echo " Got: '$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: Valid Resource ID format"
+ fi
+ else
+ echo "โ
AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: Not provided (optional)"
+ fi
+
+ # Validate AZURE_EXISTING_AI_PROJECT_RESOURCE_ID (optional, Azure Resource ID format)
+ if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
+ if [[ ! "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/(Microsoft\.MachineLearningServices/(workspaces|projects)/[^/]+|Microsoft\.CognitiveServices/accounts/[^/]+/projects/[^/]+)$ ]]; then
+ echo "โ ERROR: AZURE_EXISTING_AI_PROJECT_RESOURCE_ID is invalid. Must be a valid Azure Resource ID format:"
+ echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CognitiveServices/accounts/{accountName}/projects/{projectName}"
+ echo " Got: '$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: Valid Resource ID format"
+ fi
+ else
+ echo "โ
AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: Not provided (optional)"
+ fi
+
+ # Validate existing_webapp_url (optional, must start with https)
+ if [[ -n "$INPUT_EXISTING_WEBAPP_URL" ]]; then
+ if [[ ! "$INPUT_EXISTING_WEBAPP_URL" =~ ^https:// ]]; then
+ echo "โ ERROR: existing_webapp_url must start with 'https://', got: '$INPUT_EXISTING_WEBAPP_URL'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
existing_webapp_url: '$INPUT_EXISTING_WEBAPP_URL' is valid"
+ fi
+ else
+ echo "โ
existing_webapp_url: Not provided (will perform deployment)"
+ fi
+
+ # Fail workflow if any validation failed
+ if [[ "$VALIDATION_FAILED" == "true" ]]; then
+ echo ""
+ echo "โ Parameter validation failed. Please correct the errors above and try again."
+ exit 1
+ fi
+
+ echo ""
+ echo "โ
All input parameters validated successfully!"
+
+ # Output validated values
+ echo "passed=true" >> $GITHUB_OUTPUT
+ echo "azure_location=$LOCATION" >> $GITHUB_OUTPUT
+ echo "resource_group_name=$INPUT_RESOURCE_GROUP_NAME" >> $GITHUB_OUTPUT
+ echo "waf_enabled=$WAF_ENABLED" >> $GITHUB_OUTPUT
+ echo "exp=$EXP_ENABLED" >> $GITHUB_OUTPUT
+ echo "build_docker_image=$BUILD_DOCKER" >> $GITHUB_OUTPUT
+ echo "cleanup_resources=$CLEANUP_RESOURCES" >> $GITHUB_OUTPUT
+ echo "run_e2e_tests=$TEST_OPTION" >> $GITHUB_OUTPUT
+ echo "azure_env_log_analytics_workspace_id=$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" >> $GITHUB_OUTPUT
+ echo "azure_existing_ai_project_resource_id=$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" >> $GITHUB_OUTPUT
+ echo "existing_webapp_url=$INPUT_EXISTING_WEBAPP_URL" >> $GITHUB_OUTPUT
+
+ Run:
+ needs: validate-inputs
+ if: needs.validate-inputs.outputs.validation_passed == 'true'
+ uses: ./.github/workflows/deploy-orchestrator.yml
+ with:
+ runner_os: windows-latest
+ azure_location: ${{ needs.validate-inputs.outputs.azure_location }}
+ resource_group_name: ${{ needs.validate-inputs.outputs.resource_group_name }}
+ waf_enabled: ${{ needs.validate-inputs.outputs.waf_enabled == 'true' }}
+ EXP: ${{ needs.validate-inputs.outputs.exp == 'true' }}
+ build_docker_image: ${{ needs.validate-inputs.outputs.build_docker_image == 'true' }}
+ cleanup_resources: ${{ needs.validate-inputs.outputs.cleanup_resources == 'true' }}
+ run_e2e_tests: ${{ needs.validate-inputs.outputs.run_e2e_tests }}
+ AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ needs.validate-inputs.outputs.azure_env_log_analytics_workspace_id }}
+ AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ needs.validate-inputs.outputs.azure_existing_ai_project_resource_id }}
+ existing_webapp_url: ${{ needs.validate-inputs.outputs.existing_webapp_url }}
+ trigger_type: ${{ github.event_name }}
+ secrets: inherit
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index b6276055..49352558 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -110,7 +110,7 @@ jobs:
id: generate_solution_prefix
run: |
set -e
- COMMON_PART="ccb"
+ COMMON_PART="byocc"
TIMESTAMP=$(date +%s)
UPDATED_TIMESTAMP=$(echo $TIMESTAMP | tail -c 4)
UNIQUE_SOLUTION_PREFIX="${COMMON_PART}${UPDATED_TIMESTAMP}"
diff --git a/.github/workflows/job-cleanup-deployment.yml b/.github/workflows/job-cleanup-deployment.yml
new file mode 100644
index 00000000..e1afa455
--- /dev/null
+++ b/.github/workflows/job-cleanup-deployment.yml
@@ -0,0 +1,111 @@
+name: Cleanup Deployment Job
+
+permissions:
+ contents: read
+ actions: read
+on:
+ workflow_call:
+ inputs:
+ runner_os:
+ description: 'Runner OS (ubuntu-latest or windows-latest)'
+ required: true
+ type: string
+ trigger_type:
+ description: 'Trigger type (workflow_dispatch, pull_request, schedule)'
+ required: true
+ type: string
+ cleanup_resources:
+ description: 'Cleanup Deployed Resources'
+ required: false
+ default: false
+ type: boolean
+ existing_webapp_url:
+ description: 'Existing Container WebApp URL (Skips Deployment)'
+ required: false
+ default: ''
+ type: string
+ RESOURCE_GROUP_NAME:
+ description: 'Resource Group Name to cleanup'
+ required: true
+ type: string
+ AZURE_LOCATION:
+ description: 'Azure Location'
+ required: true
+ type: string
+ AZURE_ENV_OPENAI_LOCATION:
+ description: 'Azure OpenAI Location'
+ required: true
+ type: string
+ ENV_NAME:
+ description: 'Environment Name'
+ required: true
+ type: string
+ IMAGE_TAG:
+ description: 'Docker Image Tag'
+ required: true
+ type: string
+
+jobs:
+ cleanup-deployment:
+ runs-on: ${{ inputs.runner_os }}
+ continue-on-error: true
+ env:
+ RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ AZURE_LOCATION: ${{ inputs.AZURE_LOCATION }}
+ AZURE_ENV_OPENAI_LOCATION: ${{ inputs.AZURE_ENV_OPENAI_LOCATION }}
+ ENV_NAME: ${{ inputs.ENV_NAME }}
+ IMAGE_TAG: ${{ inputs.IMAGE_TAG }}
+ steps:
+
+ - name: Login to Azure
+ shell: bash
+ run: |
+ az login --service-principal -u ${{ secrets.AZURE_CLIENT_ID }} -p ${{ secrets.AZURE_CLIENT_SECRET }} --tenant ${{ secrets.AZURE_TENANT_ID }}
+ az account set --subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }}
+
+ - name: Delete Resource Group (Optimized Cleanup)
+ id: delete_rg
+ shell: bash
+ run: |
+ set -e
+ echo "๐๏ธ Starting optimized resource cleanup..."
+ echo "Deleting resource group: ${{ env.RESOURCE_GROUP_NAME }}"
+
+ az group delete \
+ --name "${{ env.RESOURCE_GROUP_NAME }}" \
+ --yes \
+ --no-wait
+
+ echo "โ
Resource group deletion initiated (running asynchronously)"
+ echo "Note: Resources will be cleaned up in the background"
+
+ - name: Logout from Azure
+ if: always()
+ shell: bash
+ run: |
+ azd auth logout || true
+ az logout || echo "Warning: Failed to logout from Azure CLI"
+ echo "Logged out from Azure."
+
+ - name: Generate Cleanup Job Summary
+ if: always()
+ shell: bash
+ run: |
+ echo "## ๐งน Cleanup Job Summary" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
+ echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
+ echo "| **Resource Group deletion Status** | ${{ steps.delete_rg.outcome == 'success' && 'โ
Initiated' || 'โ Failed' }} |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Resource Group** | \`${{ env.RESOURCE_GROUP_NAME }}\` |" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ if [[ "${{ steps.delete_rg.outcome }}" == "success" ]]; then
+ echo "### โ
Cleanup Details" >> $GITHUB_STEP_SUMMARY
+ echo "- Successfully initiated deletion for Resource Group \`${{ env.RESOURCE_GROUP_NAME }}\`" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "### โ Cleanup Failed" >> $GITHUB_STEP_SUMMARY
+ echo "- Cleanup process encountered an error" >> $GITHUB_STEP_SUMMARY
+ echo "- Manual cleanup may be required for:" >> $GITHUB_STEP_SUMMARY
+ echo " - Resource Group: \`${{ env.RESOURCE_GROUP_NAME }}\`" >> $GITHUB_STEP_SUMMARY
+ echo "- Check the cleanup-deployment job logs for detailed error information" >> $GITHUB_STEP_SUMMARY
+ fi
diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml
new file mode 100644
index 00000000..63212ddb
--- /dev/null
+++ b/.github/workflows/job-deploy-linux.yml
@@ -0,0 +1,363 @@
+name: Deploy Steps - Linux
+
+permissions:
+ contents: read
+ actions: read
+on:
+ workflow_call:
+ inputs:
+ ENV_NAME:
+ required: true
+ type: string
+ AZURE_ENV_OPENAI_LOCATION:
+ required: true
+ type: string
+ AZURE_LOCATION:
+ required: true
+ type: string
+ RESOURCE_GROUP_NAME:
+ required: true
+ type: string
+ IMAGE_TAG:
+ required: true
+ type: string
+ BUILD_DOCKER_IMAGE:
+ required: true
+ type: string
+ EXP:
+ required: true
+ type: string
+ WAF_ENABLED:
+ required: false
+ type: string
+ default: 'false'
+ AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID:
+ required: false
+ type: string
+ AZURE_EXISTING_AI_PROJECT_RESOURCE_ID:
+ required: false
+ type: string
+ outputs:
+ CONTAINER_WEB_APPURL:
+ description: "Container Web App URL"
+ value: ${{ jobs.deploy-linux.outputs.WEBAPP_URL }}
+
+jobs:
+ deploy-linux:
+ runs-on: ubuntu-latest
+ env:
+ AZURE_DEV_COLLECT_TELEMETRY: ${{ vars.AZURE_DEV_COLLECT_TELEMETRY }}
+ outputs:
+ WEBAPP_URL: ${{ steps.get_output_linux.outputs.WEBAPP_URL }}
+ steps:
+ - name: Checkout Code
+ uses: actions/checkout@v4
+
+ - name: Validate Workflow Input Parameters
+ shell: bash
+ env:
+ INPUT_ENV_NAME: ${{ inputs.ENV_NAME }}
+ INPUT_AZURE_ENV_OPENAI_LOCATION: ${{ inputs.AZURE_ENV_OPENAI_LOCATION }}
+ INPUT_AZURE_LOCATION: ${{ inputs.AZURE_LOCATION }}
+ INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ INPUT_IMAGE_TAG: ${{ inputs.IMAGE_TAG }}
+ INPUT_BUILD_DOCKER_IMAGE: ${{ inputs.BUILD_DOCKER_IMAGE }}
+ INPUT_EXP: ${{ inputs.EXP }}
+ INPUT_WAF_ENABLED: ${{ inputs.WAF_ENABLED }}
+ INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}
+ INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
+ run: |
+ echo "๐ Validating workflow input parameters..."
+ VALIDATION_FAILED=false
+
+ # Validate ENV_NAME (required, alphanumeric and hyphens)
+ if [[ -z "$INPUT_ENV_NAME" ]]; then
+ echo "โ ERROR: ENV_NAME is required but not provided"
+ VALIDATION_FAILED=true
+ elif [[ ! "$INPUT_ENV_NAME" =~ ^[a-zA-Z0-9-]+$ ]]; then
+ echo "โ ERROR: ENV_NAME '$INPUT_ENV_NAME' is invalid. Must contain only alphanumerics and hyphens"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
ENV_NAME: '$INPUT_ENV_NAME' is valid"
+ fi
+
+ # Validate AZURE_ENV_OPENAI_LOCATION (required, Azure region format)
+ if [[ -z "$INPUT_AZURE_ENV_OPENAI_LOCATION" ]]; then
+ echo "โ ERROR: AZURE_ENV_OPENAI_LOCATION is required but not provided"
+ VALIDATION_FAILED=true
+ elif [[ ! "$INPUT_AZURE_ENV_OPENAI_LOCATION" =~ ^[a-z0-9]+$ ]]; then
+ echo "โ ERROR: AZURE_ENV_OPENAI_LOCATION '$INPUT_AZURE_ENV_OPENAI_LOCATION' is invalid. Must contain only lowercase letters and numbers"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
AZURE_ENV_OPENAI_LOCATION: '$INPUT_AZURE_ENV_OPENAI_LOCATION' is valid"
+ fi
+
+ # Validate AZURE_LOCATION (required, Azure region format)
+ if [[ -z "$INPUT_AZURE_LOCATION" ]]; then
+ echo "โ ERROR: AZURE_LOCATION is required but not provided"
+ VALIDATION_FAILED=true
+ elif [[ ! "$INPUT_AZURE_LOCATION" =~ ^[a-z0-9]+$ ]]; then
+ echo "โ ERROR: AZURE_LOCATION '$INPUT_AZURE_LOCATION' is invalid. Must contain only lowercase letters and numbers"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
AZURE_LOCATION: '$INPUT_AZURE_LOCATION' is valid"
+ fi
+
+ # Validate RESOURCE_GROUP_NAME (required, Azure naming convention)
+ if [[ -z "$INPUT_RESOURCE_GROUP_NAME" ]]; then
+ echo "โ ERROR: RESOURCE_GROUP_NAME is required but not provided"
+ VALIDATION_FAILED=true
+ elif [[ ! "$INPUT_RESOURCE_GROUP_NAME" =~ ^[a-zA-Z0-9._\(\)-]+$ ]] || [[ "$INPUT_RESOURCE_GROUP_NAME" =~ \.$ ]]; then
+ echo "โ ERROR: RESOURCE_GROUP_NAME '$INPUT_RESOURCE_GROUP_NAME' is invalid. Must contain only alphanumerics, periods, underscores, hyphens, and parentheses. Cannot end with period."
+ VALIDATION_FAILED=true
+ elif [[ ${#INPUT_RESOURCE_GROUP_NAME} -gt 90 ]]; then
+ echo "โ ERROR: RESOURCE_GROUP_NAME '$INPUT_RESOURCE_GROUP_NAME' exceeds 90 characters"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
RESOURCE_GROUP_NAME: '$INPUT_RESOURCE_GROUP_NAME' is valid"
+ fi
+
+ # Validate IMAGE_TAG (required, Docker tag pattern)
+ if [[ -z "$INPUT_IMAGE_TAG" ]]; then
+ echo "โ ERROR: IMAGE_TAG is required but not provided"
+ VALIDATION_FAILED=true
+ elif [[ ! "$INPUT_IMAGE_TAG" =~ ^[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}$ ]]; then
+ echo "โ ERROR: IMAGE_TAG '$INPUT_IMAGE_TAG' is invalid. Must start with alphanumeric or underscore, max 128 characters"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
IMAGE_TAG: '$INPUT_IMAGE_TAG' is valid"
+ fi
+
+ # Validate BUILD_DOCKER_IMAGE (required, must be 'true' or 'false')
+ if [[ "$INPUT_BUILD_DOCKER_IMAGE" != "true" && "$INPUT_BUILD_DOCKER_IMAGE" != "false" ]]; then
+ echo "โ ERROR: BUILD_DOCKER_IMAGE must be 'true' or 'false', got: '$INPUT_BUILD_DOCKER_IMAGE'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
BUILD_DOCKER_IMAGE: '$INPUT_BUILD_DOCKER_IMAGE' is valid"
+ fi
+
+ # Validate EXP (required, must be 'true' or 'false')
+ if [[ "$INPUT_EXP" != "true" && "$INPUT_EXP" != "false" ]]; then
+ echo "โ ERROR: EXP must be 'true' or 'false', got: '$INPUT_EXP'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
EXP: '$INPUT_EXP' is valid"
+ fi
+
+ # Validate WAF_ENABLED (must be 'true' or 'false')
+ if [[ "$INPUT_WAF_ENABLED" != "true" && "$INPUT_WAF_ENABLED" != "false" ]]; then
+ echo "โ ERROR: WAF_ENABLED must be 'true' or 'false', got: '$INPUT_WAF_ENABLED'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
WAF_ENABLED: '$INPUT_WAF_ENABLED' is valid"
+ fi
+
+ # Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (optional, if provided must be valid Resource ID)
+ if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then
+ if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/microsoft\.operationalinsights/workspaces/[^/]+$ ]]; then
+ echo "โ ERROR: AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID is invalid. Must be a valid Azure Resource ID format:"
+ echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}"
+ echo " Got: '$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: Valid Resource ID format"
+ fi
+ fi
+
+ # Validate AZURE_EXISTING_AI_PROJECT_RESOURCE_ID (optional, if provided must be valid Resource ID)
+ if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
+ if [[ ! "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/(Microsoft\.MachineLearningServices/(workspaces|projects)/[^/]+|Microsoft\.CognitiveServices/accounts/[^/]+/projects/[^/]+)$ ]]; then
+ echo "โ ERROR: AZURE_EXISTING_AI_PROJECT_RESOURCE_ID is invalid. Must be a valid Azure Resource ID format:"
+ echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CognitiveServices/accounts/{accountName}/projects/{projectName}"
+ echo " Got: '$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: Valid Resource ID format"
+ fi
+ fi
+
+ # Fail workflow if any validation failed
+ if [[ "$VALIDATION_FAILED" == "true" ]]; then
+ echo ""
+ echo "โ Parameter validation failed. Please correct the errors above and try again."
+ exit 1
+ fi
+
+ echo ""
+ echo "โ
All input parameters validated successfully!"
+
+ - name: Configure Parameters Based on WAF Setting
+ shell: bash
+ env:
+ INPUT_WAF_ENABLED: ${{ inputs.WAF_ENABLED }}
+ run: |
+ if [[ "$INPUT_WAF_ENABLED" == "true" ]]; then
+ cp infra/main.waf.parameters.json infra/main.parameters.json
+ echo "โ
Successfully copied WAF parameters to main parameters file"
+ else
+ echo "๐ง Configuring Non-WAF deployment - using default main.parameters.json..."
+ fi
+
+ - name: Install azd
+ uses: Azure/setup-azd@v2
+
+ - name: Login to AZD
+ id: login-azure
+ shell: bash
+ run: |
+ az login --service-principal -u ${{ secrets.AZURE_CLIENT_ID }} -p ${{ secrets.AZURE_CLIENT_SECRET }} --tenant ${{ secrets.AZURE_TENANT_ID }}
+ az account set --subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }}
+ azd auth login --client-id ${{ secrets.AZURE_CLIENT_ID }} --client-secret ${{ secrets.AZURE_CLIENT_SECRET }} --tenant-id ${{ secrets.AZURE_TENANT_ID }}
+
+ - name: Deploy using azd up and extract values (Linux)
+ id: get_output_linux
+ shell: bash
+ env:
+ INPUT_ENV_NAME: ${{ inputs.ENV_NAME }}
+ INPUT_AZURE_ENV_OPENAI_LOCATION: ${{ inputs.AZURE_ENV_OPENAI_LOCATION }}
+ INPUT_AZURE_LOCATION: ${{ inputs.AZURE_LOCATION }}
+ INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ INPUT_IMAGE_TAG: ${{ inputs.IMAGE_TAG }}
+ INPUT_BUILD_DOCKER_IMAGE: ${{ inputs.BUILD_DOCKER_IMAGE }}
+ INPUT_EXP: ${{ inputs.EXP }}
+ INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}
+ INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
+ run: |
+ set -e
+
+ echo "Creating environment..."
+ azd env new "$INPUT_ENV_NAME" --no-prompt
+ echo "Environment created: $INPUT_ENV_NAME"
+
+ echo "Setting default subscription..."
+ azd config set defaults.subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }}
+
+ # Set additional parameters
+ azd env set AZURE_SUBSCRIPTION_ID="${{ secrets.AZURE_SUBSCRIPTION_ID }}"
+ azd env set AZURE_ENV_AI_SERVICE_LOCATION="$INPUT_AZURE_ENV_OPENAI_LOCATION"
+ azd env set AZURE_LOCATION="$INPUT_AZURE_LOCATION"
+ azd env set AZURE_RESOURCE_GROUP="$INPUT_RESOURCE_GROUP_NAME"
+ azd env set AZURE_ENV_IMAGETAG="$INPUT_IMAGE_TAG"
+
+ if [[ "$INPUT_BUILD_DOCKER_IMAGE" == "true" ]]; then
+ ACR_NAME="${{ secrets.ACR_TEST_LOGIN_SERVER }}"
+ azd env set AZURE_ENV_ACR_NAME="$ACR_NAME"
+ echo "Set ACR name to: $ACR_NAME"
+ else
+ echo "Skipping ACR name configuration (using existing image)"
+ fi
+
+ if [[ "$INPUT_EXP" == "true" ]]; then
+ echo "โ
EXP ENABLED - Setting EXP parameters..."
+
+ if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then
+ EXP_LOG_ANALYTICS_ID="$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID"
+ else
+ EXP_LOG_ANALYTICS_ID="${{ vars.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}"
+ fi
+
+ if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
+ EXP_AI_PROJECT_ID="$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID"
+ else
+ EXP_AI_PROJECT_ID="${{ vars.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}"
+ fi
+
+ echo "AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: $EXP_LOG_ANALYTICS_ID"
+ echo "AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: $EXP_AI_PROJECT_ID"
+ azd env set AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID="$EXP_LOG_ANALYTICS_ID"
+ azd env set AZURE_EXISTING_AI_PROJECT_RESOURCE_ID="$EXP_AI_PROJECT_ID"
+ else
+ echo "โ EXP DISABLED - Skipping EXP parameters"
+ fi
+
+ azd up --no-prompt
+ echo "โ
Deployment succeeded"
+
+ WEBAPP_URL=$(azd env get-value WEB_APP_URL)
+ echo "WEBAPP_URL=${WEBAPP_URL}" >> $GITHUB_ENV
+ echo "WEBAPP_URL=${WEBAPP_URL}" >> $GITHUB_OUTPUT
+
+ - name: Setup Python Environment for Post-Deployment
+ shell: bash
+ run: |
+ echo "Setting up Python environment..."
+ python3 --version
+ pip3 --version
+
+ # Upgrade pip
+ python3 -m pip install --upgrade pip
+
+ - name: Run Data Upload Scripts
+ shell: bash
+ run: |
+ echo "๐ Running data upload scripts to populate product catalogs and create search indexes..."
+
+ # Navigate to workspace root
+ cd $GITHUB_WORKSPACE
+
+ # Run the data upload script (it will pull parameters from azd env and install its own requirements)
+ bash ./infra/scripts/data_scripts/run_upload_data_scripts.sh
+
+ if [[ $? -eq 0 ]]; then
+ echo "โ
Data upload completed successfully"
+ else
+ echo "โ Data upload failed"
+ exit 1
+ fi
+ - name: Run Agent Creation Scripts
+ shell: bash
+ run: |
+ echo "๐ค Running agent creation scripts to set up Azure AI Foundry agents..."
+
+ # Navigate to workspace root
+ cd $GITHUB_WORKSPACE
+
+ # Run the agent creation script (it will pull parameters from azd env and install its own requirements)
+ bash ./infra/scripts/agent_scripts/run_create_agents_scripts.sh
+
+ if [[ $? -eq 0 ]]; then
+ echo "โ
Agent creation completed successfully"
+ else
+ echo "โ Agent creation failed"
+ exit 1
+ fi
+
+ - name: Generate Deployment Summary
+ if: always()
+ shell: bash
+ env:
+ INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ INPUT_AZURE_LOCATION: ${{ inputs.AZURE_LOCATION }}
+ INPUT_AZURE_ENV_OPENAI_LOCATION: ${{ inputs.AZURE_ENV_OPENAI_LOCATION }}
+ INPUT_IMAGE_TAG: ${{ inputs.IMAGE_TAG }}
+ INPUT_WAF_ENABLED: ${{ inputs.WAF_ENABLED }}
+ INPUT_EXP: ${{ inputs.EXP }}
+ CONFIG_TYPE : ${{ inputs.WAF_ENABLED == 'true' && inputs.EXP == 'true' && 'WAF + EXP' || inputs.WAF_ENABLED == 'true' && inputs.EXP != 'true' && 'WAF + Non-EXP' || inputs.WAF_ENABLED != 'true' && inputs.EXP == 'true' && 'Non-WAF + EXP' || 'Non-WAF + Non-EXP' }}
+ run: |
+ echo "## ๐ Deploy Job Summary (Linux)" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
+ echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
+ echo "| **Job Status** | ${{ job.status == 'success' && 'โ
Success' || 'โ Failed' }} |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Resource Group** | \`$INPUT_RESOURCE_GROUP_NAME \` |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Configuration Type** | \`$CONFIG_TYPE\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Azure Region (Infrastructure)** | \`$INPUT_AZURE_LOCATION\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Azure OpenAI Region** | \`$INPUT_AZURE_ENV_OPENAI_LOCATION\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Docker Image Tag** | \`$INPUT_IMAGE_TAG\` |" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ if [[ "${{ job.status }}" == "success" ]]; then
+ echo "### โ
Deployment Details" >> $GITHUB_STEP_SUMMARY
+ echo "- **Container Web App URL**: [${{ steps.get_output_linux.outputs.WEBAPP_URL }}](${{ steps.get_output_linux.outputs.WEBAPP_URL }})" >> $GITHUB_STEP_SUMMARY
+ echo "- Successfully deployed to Azure with all resources configured" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "### โ Deployment Failed" >> $GITHUB_STEP_SUMMARY
+ echo "- Deployment process encountered an error" >> $GITHUB_STEP_SUMMARY
+ echo "- Check the deployment steps above for detailed error information" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ - name: Logout from Azure
+ if: always()
+ shell: bash
+ run: |
+ az logout || true
+ echo "Logged out from Azure."
diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml
new file mode 100644
index 00000000..77f05fa9
--- /dev/null
+++ b/.github/workflows/job-deploy-windows.yml
@@ -0,0 +1,370 @@
+name: Deploy Steps - Windows
+
+permissions:
+ contents: read
+ actions: read
+
+on:
+ workflow_call:
+ inputs:
+ ENV_NAME:
+ required: true
+ type: string
+ AZURE_ENV_OPENAI_LOCATION:
+ required: true
+ type: string
+ AZURE_LOCATION:
+ required: true
+ type: string
+ RESOURCE_GROUP_NAME:
+ required: true
+ type: string
+ IMAGE_TAG:
+ required: true
+ type: string
+ BUILD_DOCKER_IMAGE:
+ required: true
+ type: string
+ EXP:
+ required: true
+ type: string
+ WAF_ENABLED:
+ required: false
+ type: string
+ default: 'false'
+ AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID:
+ required: false
+ type: string
+ AZURE_EXISTING_AI_PROJECT_RESOURCE_ID:
+ required: false
+ type: string
+ outputs:
+ CONTAINER_WEB_APPURL:
+ description: "Container Web App URL"
+ value: ${{ jobs.deploy-windows.outputs.WEBAPP_URL }}
+
+jobs:
+ deploy-windows:
+ runs-on: windows-latest
+ env:
+ AZURE_DEV_COLLECT_TELEMETRY: ${{ vars.AZURE_DEV_COLLECT_TELEMETRY }}
+ outputs:
+ WEBAPP_URL: ${{ steps.get_output_windows.outputs.WEBAPP_URL }}
+ steps:
+ - name: Checkout Code
+ uses: actions/checkout@v4
+
+ - name: Validate Workflow Input Parameters
+ shell: bash
+ env:
+ INPUT_ENV_NAME: ${{ inputs.ENV_NAME }}
+ INPUT_AZURE_ENV_OPENAI_LOCATION: ${{ inputs.AZURE_ENV_OPENAI_LOCATION }}
+ INPUT_AZURE_LOCATION: ${{ inputs.AZURE_LOCATION }}
+ INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ INPUT_IMAGE_TAG: ${{ inputs.IMAGE_TAG }}
+ INPUT_BUILD_DOCKER_IMAGE: ${{ inputs.BUILD_DOCKER_IMAGE }}
+ INPUT_EXP: ${{ inputs.EXP }}
+ INPUT_WAF_ENABLED: ${{ inputs.WAF_ENABLED }}
+ INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}
+ INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
+ run: |
+ echo "๐ Validating workflow input parameters..."
+ VALIDATION_FAILED=false
+
+ # Validate ENV_NAME (required, alphanumeric and hyphens)
+ if [[ -z "$INPUT_ENV_NAME" ]]; then
+ echo "โ ERROR: ENV_NAME is required but not provided"
+ VALIDATION_FAILED=true
+ elif [[ ! "$INPUT_ENV_NAME" =~ ^[a-zA-Z0-9-]+$ ]]; then
+ echo "โ ERROR: ENV_NAME '$INPUT_ENV_NAME' is invalid. Must contain only alphanumerics and hyphens"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
ENV_NAME: '$INPUT_ENV_NAME' is valid"
+ fi
+
+ # Validate AZURE_ENV_OPENAI_LOCATION (required, Azure region format)
+ if [[ -z "$INPUT_AZURE_ENV_OPENAI_LOCATION" ]]; then
+ echo "โ ERROR: AZURE_ENV_OPENAI_LOCATION is required but not provided"
+ VALIDATION_FAILED=true
+ elif [[ ! "$INPUT_AZURE_ENV_OPENAI_LOCATION" =~ ^[a-z0-9]+$ ]]; then
+ echo "โ ERROR: AZURE_ENV_OPENAI_LOCATION '$INPUT_AZURE_ENV_OPENAI_LOCATION' is invalid. Must contain only lowercase letters and numbers"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
AZURE_ENV_OPENAI_LOCATION: '$INPUT_AZURE_ENV_OPENAI_LOCATION' is valid"
+ fi
+
+ # Validate AZURE_LOCATION (required, Azure region format)
+ if [[ -z "$INPUT_AZURE_LOCATION" ]]; then
+ echo "โ ERROR: AZURE_LOCATION is required but not provided"
+ VALIDATION_FAILED=true
+ elif [[ ! "$INPUT_AZURE_LOCATION" =~ ^[a-z0-9]+$ ]]; then
+ echo "โ ERROR: AZURE_LOCATION '$INPUT_AZURE_LOCATION' is invalid. Must contain only lowercase letters and numbers"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
AZURE_LOCATION: '$INPUT_AZURE_LOCATION' is valid"
+ fi
+
+ # Validate RESOURCE_GROUP_NAME (required, Azure naming convention)
+ if [[ -z "$INPUT_RESOURCE_GROUP_NAME" ]]; then
+ echo "โ ERROR: RESOURCE_GROUP_NAME is required but not provided"
+ VALIDATION_FAILED=true
+ elif [[ ! "$INPUT_RESOURCE_GROUP_NAME" =~ ^[a-zA-Z0-9._\(\)-]+$ ]] || [[ "$INPUT_RESOURCE_GROUP_NAME" =~ \.$ ]]; then
+ echo "โ ERROR: RESOURCE_GROUP_NAME '$INPUT_RESOURCE_GROUP_NAME' is invalid. Must contain only alphanumerics, periods, underscores, hyphens, and parentheses. Cannot end with period."
+ VALIDATION_FAILED=true
+ elif [[ ${#INPUT_RESOURCE_GROUP_NAME} -gt 90 ]]; then
+ echo "โ ERROR: RESOURCE_GROUP_NAME '$INPUT_RESOURCE_GROUP_NAME' exceeds 90 characters"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
RESOURCE_GROUP_NAME: '$INPUT_RESOURCE_GROUP_NAME' is valid"
+ fi
+
+ # Validate IMAGE_TAG (required, Docker tag pattern)
+ if [[ -z "$INPUT_IMAGE_TAG" ]]; then
+ echo "โ ERROR: IMAGE_TAG is required but not provided"
+ VALIDATION_FAILED=true
+ elif [[ ! "$INPUT_IMAGE_TAG" =~ ^[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}$ ]]; then
+ echo "โ ERROR: IMAGE_TAG '$INPUT_IMAGE_TAG' is invalid. Must start with alphanumeric or underscore, max 128 characters"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
IMAGE_TAG: '$INPUT_IMAGE_TAG' is valid"
+ fi
+
+ # Validate BUILD_DOCKER_IMAGE (required, must be 'true' or 'false')
+ if [[ "$INPUT_BUILD_DOCKER_IMAGE" != "true" && "$INPUT_BUILD_DOCKER_IMAGE" != "false" ]]; then
+ echo "โ ERROR: BUILD_DOCKER_IMAGE must be 'true' or 'false', got: '$INPUT_BUILD_DOCKER_IMAGE'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
BUILD_DOCKER_IMAGE: '$INPUT_BUILD_DOCKER_IMAGE' is valid"
+ fi
+
+ # Validate EXP (required, must be 'true' or 'false')
+ if [[ "$INPUT_EXP" != "true" && "$INPUT_EXP" != "false" ]]; then
+ echo "โ ERROR: EXP must be 'true' or 'false', got: '$INPUT_EXP'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
EXP: '$INPUT_EXP' is valid"
+ fi
+
+ # Validate WAF_ENABLED (must be 'true' or 'false')
+ if [[ "$INPUT_WAF_ENABLED" != "true" && "$INPUT_WAF_ENABLED" != "false" ]]; then
+ echo "โ ERROR: WAF_ENABLED must be 'true' or 'false', got: '$INPUT_WAF_ENABLED'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
WAF_ENABLED: '$INPUT_WAF_ENABLED' is valid"
+ fi
+
+ # Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (optional, if provided must be valid Resource ID)
+ if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then
+ if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/microsoft\.operationalinsights/workspaces/[^/]+$ ]]; then
+ echo "โ ERROR: AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID is invalid. Must be a valid Azure Resource ID format:"
+ echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}"
+ echo " Got: '$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: Valid Resource ID format"
+ fi
+ fi
+
+ # Validate AZURE_EXISTING_AI_PROJECT_RESOURCE_ID (optional, if provided must be valid Resource ID)
+ if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
+ if [[ ! "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/(Microsoft\.MachineLearningServices/(workspaces|projects)/[^/]+|Microsoft\.CognitiveServices/accounts/[^/]+/projects/[^/]+)$ ]]; then
+ echo "โ ERROR: AZURE_EXISTING_AI_PROJECT_RESOURCE_ID is invalid. Must be a valid Azure Resource ID format:"
+ echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CognitiveServices/accounts/{accountName}/projects/{projectName}"
+ echo " Got: '$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: Valid Resource ID format"
+ fi
+ fi
+
+ # Fail workflow if any validation failed
+ if [[ "$VALIDATION_FAILED" == "true" ]]; then
+ echo ""
+ echo "โ Parameter validation failed. Please correct the errors above and try again."
+ exit 1
+ fi
+
+ echo ""
+ echo "โ
All input parameters validated successfully!"
+
+ - name: Configure Parameters Based on WAF Setting
+ shell: bash
+ env:
+ INPUT_WAF_ENABLED: ${{ inputs.WAF_ENABLED }}
+ run: |
+ if [[ "$INPUT_WAF_ENABLED" == "true" ]]; then
+ cp infra/main.waf.parameters.json infra/main.parameters.json
+ echo "โ
Successfully copied WAF parameters to main parameters file"
+ else
+ echo "๐ง Configuring Non-WAF deployment - using default main.parameters.json..."
+ fi
+
+ - name: Install azd
+ uses: Azure/setup-azd@v2
+
+ - name: Login to AZD
+ id: login-azure
+ shell: bash
+ run: |
+ az login --service-principal -u ${{ secrets.AZURE_CLIENT_ID }} -p ${{ secrets.AZURE_CLIENT_SECRET }} --tenant ${{ secrets.AZURE_TENANT_ID }}
+ az account set --subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }}
+ azd auth login --client-id ${{ secrets.AZURE_CLIENT_ID }} --client-secret ${{ secrets.AZURE_CLIENT_SECRET }} --tenant-id ${{ secrets.AZURE_TENANT_ID }}
+
+
+ - name: Deploy using azd up and extract values (Windows)
+ id: get_output_windows
+ shell: pwsh
+ env:
+ INPUT_ENV_NAME: ${{ inputs.ENV_NAME }}
+ INPUT_AZURE_ENV_OPENAI_LOCATION: ${{ inputs.AZURE_ENV_OPENAI_LOCATION }}
+ INPUT_AZURE_LOCATION: ${{ inputs.AZURE_LOCATION }}
+ INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ INPUT_IMAGE_TAG: ${{ inputs.IMAGE_TAG }}
+ INPUT_BUILD_DOCKER_IMAGE: ${{ inputs.BUILD_DOCKER_IMAGE }}
+ INPUT_EXP: ${{ inputs.EXP }}
+ INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}
+ INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
+ run: |
+ $ErrorActionPreference = "Stop"
+ Write-Host "Starting azd deployment..."
+ Write-Host "EXP: $env:INPUT_EXP"
+ Write-Host "Using Docker Image Tag: $env:INPUT_IMAGE_TAG"
+
+ Write-Host "Creating environment..."
+ azd env new "$env:INPUT_ENV_NAME" --no-prompt
+ Write-Host "Environment created: $env:INPUT_ENV_NAME"
+
+ Write-Host "Setting default subscription..."
+ azd config set defaults.subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }}
+
+ # Set additional parameters
+ azd env set AZURE_SUBSCRIPTION_ID="${{ secrets.AZURE_SUBSCRIPTION_ID }}"
+ azd env set AZURE_ENV_AI_SERVICE_LOCATION="$env:INPUT_AZURE_ENV_OPENAI_LOCATION"
+ azd env set AZURE_LOCATION="$env:INPUT_AZURE_LOCATION"
+ azd env set AZURE_RESOURCE_GROUP="$env:INPUT_RESOURCE_GROUP_NAME"
+ azd env set AZURE_ENV_IMAGETAG="$env:INPUT_IMAGE_TAG"
+
+ # Set ACR name only when building Docker image
+ if ("$env:INPUT_BUILD_DOCKER_IMAGE" -eq "true") {
+ $ACR_NAME = "${{ secrets.ACR_TEST_LOGIN_SERVER }}"
+ azd env set AZURE_ENV_ACR_NAME="$ACR_NAME"
+ Write-Host "Set ACR name to: $ACR_NAME"
+ } else {
+ Write-Host "Skipping ACR name configuration (using existing image)"
+ }
+
+ if ("$env:INPUT_EXP" -eq "true") {
+ Write-Host "EXP ENABLED โ
- Setting EXP parameters..."
+
+ # Set EXP variables dynamically
+ if ("$env:INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" -ne "") {
+ $EXP_LOG_ANALYTICS_ID = "$env:INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID"
+ } else {
+ $EXP_LOG_ANALYTICS_ID = "${{ vars.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}"
+ }
+
+ if ("$env:INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" -ne "") {
+ $EXP_AI_PROJECT_ID = "$env:INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID"
+ } else {
+ $EXP_AI_PROJECT_ID = "${{ vars.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}"
+ }
+
+ Write-Host "AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: $EXP_LOG_ANALYTICS_ID"
+ Write-Host "AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: $EXP_AI_PROJECT_ID"
+ azd env set AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID="$EXP_LOG_ANALYTICS_ID"
+ azd env set AZURE_EXISTING_AI_PROJECT_RESOURCE_ID="$EXP_AI_PROJECT_ID"
+ } else {
+ Write-Host "EXP DISABLED - Skipping EXP parameters"
+ }
+
+ # Deploy using azd up
+ azd up --no-prompt
+ Write-Host "โ
Deployment succeeded."
+
+ $WEBAPP_URL = azd env get-value WEB_APP_URL
+ "WEBAPP_URL=$WEBAPP_URL" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
+ "WEBAPP_URL=$WEBAPP_URL" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
+
+ - name: Run Post-Deployment Data Upload Scripts
+ shell: pwsh
+ run: |
+ Write-Host "Running data upload scripts..."
+
+ # Get service principal object ID for role assignments
+ $SP_OBJECT_ID = az ad sp show --id ${{ secrets.AZURE_CLIENT_ID }} --query id -o tsv
+ Write-Host "Service Principal Object ID: $SP_OBJECT_ID"
+
+ # Export for scripts to use
+ $env:AZURE_PRINCIPAL_ID = $SP_OBJECT_ID
+ $env:AZURE_CLIENT_ID = "${{ secrets.AZURE_CLIENT_ID }}"
+
+ bash ./$env:INFRA_PATH/scripts/data_scripts/run_upload_data_scripts.sh
+ Write-Host "โ
Data upload completed successfully"
+ - name: Setup Python 3.11 for Agent Scripts
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.11'
+
+ - name: Run Post-Deployment Agent Creation Scripts
+ shell: pwsh
+ run: |
+ Write-Host "Running agent creation scripts..."
+
+ # Get service principal object ID for role assignments
+ $SP_OBJECT_ID = az ad sp show --id ${{ secrets.AZURE_CLIENT_ID }} --query id -o tsv
+ Write-Host "Service Principal Object ID: $SP_OBJECT_ID"
+
+ # Export for scripts to use
+ $env:AZURE_PRINCIPAL_ID = $SP_OBJECT_ID
+ $env:AZURE_CLIENT_ID = "${{ secrets.AZURE_CLIENT_ID }}"
+
+ bash ./$env:INFRA_PATH/scripts/agent_scripts/run_create_agents_scripts.sh
+
+ - name: Generate Deployment Summary
+ if: always()
+ shell: bash
+ env:
+ INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ INPUT_AZURE_LOCATION: ${{ inputs.AZURE_LOCATION }}
+ INPUT_AZURE_ENV_OPENAI_LOCATION: ${{ inputs.AZURE_ENV_OPENAI_LOCATION }}
+ INPUT_IMAGE_TAG: ${{ inputs.IMAGE_TAG }}
+ INPUT_WAF_ENABLED: ${{ inputs.WAF_ENABLED }}
+ INPUT_EXP: ${{ inputs.EXP }}
+ JOB_STATUS: ${{ job.status }}
+ run: |
+ echo "## ๐ Deploy Job Summary (Windows)" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
+ echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
+ echo "| **Job Status** | ${{ job.status == 'success' && 'โ
Success' || 'โ Failed' }} |" >> $GITHUB_STEP_SUMMARY
+ CONFIG_TYPE="Non-WAF + Non-EXP"
+ if [[ "$INPUT_WAF_ENABLED" == "true" && "$INPUT_EXP" == "true" ]]; then
+ CONFIG_TYPE="WAF + EXP"
+ elif [[ "$INPUT_WAF_ENABLED" == "true" && "$INPUT_EXP" != "true" ]]; then
+ CONFIG_TYPE="WAF + Non-EXP"
+ elif [[ "$INPUT_WAF_ENABLED" != "true" && "$INPUT_EXP" == "true" ]]; then
+ CONFIG_TYPE="Non-WAF + EXP"
+ fi
+ echo "| **Configuration Type** | \`$CONFIG_TYPE\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Resource Group** | \`$INPUT_RESOURCE_GROUP_NAME\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Azure Region (Infrastructure)** | \`$INPUT_AZURE_LOCATION\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Azure OpenAI Region** | \`$INPUT_AZURE_ENV_OPENAI_LOCATION\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Docker Image Tag** | \`$INPUT_IMAGE_TAG\` |" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ if [ "$JOB_STATUS" == "success" ]; then
+ echo "### โ
Deployment Details" >> $GITHUB_STEP_SUMMARY
+ echo "- **Container Web App URL**: [${{ steps.get_output_windows.outputs.WEBAPP_URL }}](${{ steps.get_output_windows.outputs.WEBAPP_URL }})" >> $GITHUB_STEP_SUMMARY
+ echo "- Successfully deployed to Azure with all resources configured" >> $GITHUB_STEP_SUMMARY
+ echo "- Schemas registered and sample data uploaded successfully" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "### โ Deployment Failed" >> $GITHUB_STEP_SUMMARY
+ echo "- Deployment process encountered an error" >> $GITHUB_STEP_SUMMARY
+ echo "- Check the deployment steps above for detailed error information" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ - name: Logout from Azure
+ if: always()
+ shell: bash
+ run: |
+ az logout || true
+ echo "Logged out from Azure."
diff --git a/.github/workflows/job-deploy.yml b/.github/workflows/job-deploy.yml
new file mode 100644
index 00000000..7c44f64e
--- /dev/null
+++ b/.github/workflows/job-deploy.yml
@@ -0,0 +1,497 @@
+name: Deploy Job
+
+permissions:
+ contents: read
+ actions: read
+on:
+ workflow_call:
+ inputs:
+ trigger_type:
+ description: 'Trigger type (workflow_dispatch, pull_request, schedule)'
+ required: true
+ type: string
+ runner_os:
+ description: 'Runner OS (ubuntu-latest or windows-latest)'
+ required: true
+ type: string
+ azure_location:
+ description: 'Azure Location For Deployment'
+ required: false
+ default: 'australiaeast'
+ type: string
+ resource_group_name:
+ description: 'Resource Group Name (Optional)'
+ required: false
+ default: ''
+ type: string
+ waf_enabled:
+ description: 'Enable WAF'
+ required: false
+ default: false
+ type: boolean
+ EXP:
+ description: 'Enable EXP'
+ required: false
+ default: false
+ type: boolean
+ build_docker_image:
+ description: 'Build And Push Docker Image (Optional)'
+ required: false
+ default: false
+ type: boolean
+ cleanup_resources:
+ description: 'Cleanup Deployed Resources'
+ required: false
+ default: false
+ type: boolean
+ run_e2e_tests:
+ description: 'Run End-to-End Tests'
+ required: false
+ default: 'GoldenPath-Testing'
+ type: string
+ existing_webapp_url:
+ description: 'Existing Container WebApp URL (Skips Deployment)'
+ required: false
+ default: ''
+ type: string
+ AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID:
+ description: 'Log Analytics Workspace ID (Optional)'
+ required: false
+ default: ''
+ type: string
+ AZURE_EXISTING_AI_PROJECT_RESOURCE_ID:
+ description: 'AI Project Resource ID (Optional)'
+ required: false
+ default: ''
+ type: string
+ docker_image_tag:
+ description: 'Docker Image Tag from build job'
+ required: false
+ default: ''
+ type: string
+ outputs:
+ RESOURCE_GROUP_NAME:
+ description: "Resource Group Name"
+ value: ${{ jobs.azure-setup.outputs.RESOURCE_GROUP_NAME }}
+ CONTAINER_WEB_APPURL:
+ description: "Container Web App URL"
+ value: ${{ jobs.deploy-linux.outputs.CONTAINER_WEB_APPURL || jobs.deploy-windows.outputs.CONTAINER_WEB_APPURL }}
+ ENV_NAME:
+ description: "Environment Name"
+ value: ${{ jobs.azure-setup.outputs.ENV_NAME }}
+ AZURE_LOCATION:
+ description: "Azure Location"
+ value: ${{ jobs.azure-setup.outputs.AZURE_LOCATION }}
+ AZURE_ENV_OPENAI_LOCATION:
+ description: "Azure OpenAI Location"
+ value: ${{ jobs.azure-setup.outputs.AZURE_ENV_OPENAI_LOCATION }}
+ IMAGE_TAG:
+ description: "Docker Image Tag Used"
+ value: ${{ jobs.azure-setup.outputs.IMAGE_TAG }}
+ QUOTA_FAILED:
+ description: "Quota Check Failed Flag"
+ value: ${{ jobs.azure-setup.outputs.QUOTA_FAILED }}
+
+env:
+ GPT_MIN_CAPACITY: 150
+ BRANCH_NAME: ${{ github.event.workflow_run.head_branch || github.head_ref || github.ref_name }}
+ WAF_ENABLED: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.waf_enabled || false) || false }}
+ EXP: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.EXP || false) || false }}
+ CLEANUP_RESOURCES: ${{ inputs.trigger_type != 'workflow_dispatch' || inputs.cleanup_resources }}
+ RUN_E2E_TESTS: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.run_e2e_tests || 'GoldenPath-Testing') || 'GoldenPath-Testing' }}
+ BUILD_DOCKER_IMAGE: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.build_docker_image || false) || false }}
+
+jobs:
+ azure-setup:
+ name: Azure Setup
+ if: inputs.trigger_type != 'workflow_dispatch' || inputs.existing_webapp_url == '' || inputs.existing_webapp_url == null
+ runs-on: ubuntu-latest
+ outputs:
+ RESOURCE_GROUP_NAME: ${{ steps.check_create_rg.outputs.RESOURCE_GROUP_NAME }}
+ ENV_NAME: ${{ steps.generate_env_name.outputs.ENV_NAME }}
+ AZURE_LOCATION: ${{ steps.set_region.outputs.AZURE_LOCATION }}
+ AZURE_ENV_OPENAI_LOCATION: ${{ steps.set_region.outputs.AZURE_ENV_OPENAI_LOCATION }}
+ IMAGE_TAG: ${{ steps.determine_image_tag.outputs.IMAGE_TAG }}
+ QUOTA_FAILED: ${{ steps.quota_failure_output.outputs.QUOTA_FAILED }}
+
+ steps:
+ - name: Validate Workflow Input Parameters
+ shell: bash
+ env:
+ INPUT_AZURE_LOCATION: ${{ inputs.azure_location }}
+ INPUT_RESOURCE_GROUP_NAME: ${{ inputs.resource_group_name }}
+ INPUT_EXP: ${{ inputs.EXP }}
+ INPUT_WAF_ENABLED: ${{ inputs.waf_enabled }}
+ INPUT_CLEANUP_RESOURCES: ${{ inputs.cleanup_resources }}
+ INPUT_RUN_E2E_TESTS: ${{ inputs.run_e2e_tests }}
+ INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}
+ INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
+ INPUT_DOCKER_IMAGE_TAG: ${{ inputs.docker_image_tag }}
+ run: |
+ echo "๐ Validating workflow input parameters..."
+ VALIDATION_FAILED=false
+
+ # Validate azure_location (Azure region format)
+ if [[ -n "$INPUT_AZURE_LOCATION" ]]; then
+ if [[ ! "$INPUT_AZURE_LOCATION" =~ ^[a-z0-9]+$ ]]; then
+ echo "โ ERROR: azure_location '$INPUT_AZURE_LOCATION' is invalid. Must contain only lowercase letters and numbers (e.g., 'australiaeast', 'westus2')"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
azure_location: '$INPUT_AZURE_LOCATION' is valid"
+ fi
+ fi
+
+ # Validate resource_group_name (Azure resource group naming convention)
+ if [[ -n "$INPUT_RESOURCE_GROUP_NAME" ]]; then
+ if [[ ! "$INPUT_RESOURCE_GROUP_NAME" =~ ^[a-zA-Z0-9._\(\)-]+$ ]] || [[ "$INPUT_RESOURCE_GROUP_NAME" =~ \.$ ]]; then
+ echo "โ ERROR: resource_group_name '$INPUT_RESOURCE_GROUP_NAME' is invalid. Must contain only alphanumerics, periods, underscores, hyphens, and parentheses. Cannot end with period."
+ VALIDATION_FAILED=true
+ elif [[ ${#INPUT_RESOURCE_GROUP_NAME} -gt 90 ]]; then
+ echo "โ ERROR: resource_group_name '$INPUT_RESOURCE_GROUP_NAME' exceeds 90 characters"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
resource_group_name: '$INPUT_RESOURCE_GROUP_NAME' is valid"
+ fi
+ fi
+
+ # Validate waf_enabled (boolean)
+ if [[ "$INPUT_WAF_ENABLED" != "true" && "$INPUT_WAF_ENABLED" != "false" ]]; then
+ echo "โ ERROR: waf_enabled must be 'true' or 'false', got: '$INPUT_WAF_ENABLED'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
waf_enabled: '$INPUT_WAF_ENABLED' is valid"
+ fi
+
+ # Validate EXP (boolean)
+ if [[ "$INPUT_EXP" != "true" && "$INPUT_EXP" != "false" ]]; then
+ echo "โ ERROR: EXP must be 'true' or 'false', got: '$INPUT_EXP'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
EXP: '$INPUT_EXP' is valid"
+ fi
+
+ # Validate cleanup_resources (boolean)
+ if [[ "$INPUT_CLEANUP_RESOURCES" != "true" && "$INPUT_CLEANUP_RESOURCES" != "false" ]]; then
+ echo "โ ERROR: cleanup_resources must be 'true' or 'false', got: '$INPUT_CLEANUP_RESOURCES'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
cleanup_resources: '$INPUT_CLEANUP_RESOURCES' is valid"
+ fi
+
+ # Validate run_e2e_tests (specific allowed values)
+ if [[ -n "$INPUT_RUN_E2E_TESTS" ]]; then
+ ALLOWED_VALUES=("None" "GoldenPath-Testing" "Smoke-Testing")
+ if [[ ! " ${ALLOWED_VALUES[@]} " =~ " ${INPUT_RUN_E2E_TESTS} " ]]; then
+ echo "โ ERROR: run_e2e_tests '$INPUT_RUN_E2E_TESTS' is invalid. Allowed values: ${ALLOWED_VALUES[*]}"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
run_e2e_tests: '$INPUT_RUN_E2E_TESTS' is valid"
+ fi
+ fi
+
+ # Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (Azure Resource ID format)
+ if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then
+ if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/microsoft\.operationalinsights/workspaces/[^/]+$ ]]; then
+ echo "โ ERROR: AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID is invalid. Must be a valid Azure Resource ID format:"
+ echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}"
+ echo " Got: '$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: Valid Resource ID format"
+ fi
+ fi
+
+ # Validate AZURE_EXISTING_AI_PROJECT_RESOURCE_ID (Azure Resource ID format)
+ if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
+ if [[ ! "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/(Microsoft\.MachineLearningServices/(workspaces|projects)/[^/]+|Microsoft\.CognitiveServices/accounts/[^/]+/projects/[^/]+)$ ]]; then
+ echo "โ ERROR: AZURE_EXISTING_AI_PROJECT_RESOURCE_ID is invalid. Must be a valid Azure Resource ID format:"
+ echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CognitiveServices/accounts/{accountName}/projects/{projectName}"
+ echo " Got: '$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: Valid Resource ID format"
+ fi
+ fi
+
+ # Validate docker_image_tag (Docker tag pattern)
+ if [[ -n "$INPUT_DOCKER_IMAGE_TAG" ]]; then
+ # Docker tags: lowercase and uppercase letters, digits, underscores, periods, and hyphens
+ # Cannot start with period or hyphen, max 128 characters
+ if [[ ! "$INPUT_DOCKER_IMAGE_TAG" =~ ^[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}$ ]]; then
+ echo "โ ERROR: docker_image_tag '$INPUT_DOCKER_IMAGE_TAG' is invalid. Must:"
+ echo " - Start with alphanumeric or underscore"
+ echo " - Contain only alphanumerics, underscores, periods, hyphens"
+ echo " - Be max 128 characters"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
docker_image_tag: '$INPUT_DOCKER_IMAGE_TAG' is valid"
+ fi
+ fi
+
+ # Fail workflow if any validation failed
+ if [[ "$VALIDATION_FAILED" == "true" ]]; then
+ echo ""
+ echo "โ Parameter validation failed. Please correct the errors above and try again."
+ exit 1
+ fi
+
+ echo ""
+ echo "โ
All input parameters validated successfully!"
+
+ - name: Validate and Auto-Configure EXP
+ shell: bash
+ env:
+ INPUT_EXP: ${{ inputs.EXP }}
+ INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}
+ INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
+ run: |
+ echo "๐ Validating EXP configuration..."
+
+ if [[ "$INPUT_EXP" != "true" ]]; then
+ if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]] || [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
+ echo "๐ง AUTO-ENABLING EXP: EXP parameter values were provided but EXP was not explicitly enabled."
+ echo ""
+ echo "You provided values for:"
+ [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]] && echo " - Azure Log Analytics Workspace ID: '$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID'"
+ [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]] && echo " - Azure AI Project Resource ID: '$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID'"
+ echo ""
+ echo "โ
Automatically enabling EXP to use these values."
+ echo "EXP=true" >> $GITHUB_ENV
+ echo "๐ EXP has been automatically enabled for this deployment."
+ fi
+ fi
+
+ - name: Checkout Code
+ uses: actions/checkout@v4
+
+ - name: Login to Azure
+ shell: bash
+ run: |
+ az login --service-principal -u ${{ secrets.AZURE_CLIENT_ID }} -p ${{ secrets.AZURE_CLIENT_SECRET }} --tenant ${{ secrets.AZURE_TENANT_ID }}
+ az account set --subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }}
+
+ - name: Run Quota Check
+ id: quota-check
+ env:
+ AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
+ AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
+ AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
+ AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
+ AZURE_REGIONS: ${{ vars.AZURE_REGIONS }}
+ GPT_MIN_CAPACITY: ${{ env.GPT_MIN_CAPACITY }}
+ run: |
+
+ chmod +x infra/scripts/checkquota.sh
+ if ! infra/scripts/checkquota.sh; then
+ # If quota check fails due to insufficient quota, set the flag
+ if grep -q "No region with sufficient quota found" infra/scripts/checkquota.sh; then
+ echo "QUOTA_FAILED=true" >> $GITHUB_ENV
+ fi
+ exit 1 # Fail the pipeline if any other failure occurs
+ fi
+
+ - name: Set Quota Failure Output
+ id: quota_failure_output
+ if: env.QUOTA_FAILED == 'true'
+ shell: bash
+ run: |
+ echo "QUOTA_FAILED=true" >> $GITHUB_OUTPUT
+ echo "Quota check failed - will notify via separate notification job"
+
+ - name: Fail Pipeline if Quota Check Fails
+ if: env.QUOTA_FAILED == 'true'
+ shell: bash
+ run: exit 1
+
+ - name: Set Deployment Region
+ id: set_region
+ shell: bash
+ env:
+ INPUT_TRIGGER_TYPE: ${{ inputs.trigger_type }}
+ INPUT_AZURE_LOCATION: ${{ inputs.azure_location }}
+ run: |
+ echo "Selected Region from Quota Check: $VALID_REGION"
+ echo "AZURE_ENV_OPENAI_LOCATION=$VALID_REGION" >> $GITHUB_ENV
+ echo "AZURE_ENV_OPENAI_LOCATION=$VALID_REGION" >> $GITHUB_OUTPUT
+
+ if [[ "$INPUT_TRIGGER_TYPE" == "workflow_dispatch" && -n "$INPUT_AZURE_LOCATION" ]]; then
+ USER_SELECTED_LOCATION="$INPUT_AZURE_LOCATION"
+ echo "Using user-selected Azure location: $USER_SELECTED_LOCATION"
+ echo "AZURE_LOCATION=$USER_SELECTED_LOCATION" >> $GITHUB_ENV
+ echo "AZURE_LOCATION=$USER_SELECTED_LOCATION" >> $GITHUB_OUTPUT
+ else
+ echo "Using location from quota check for automatic triggers: $VALID_REGION"
+ echo "AZURE_LOCATION=$VALID_REGION" >> $GITHUB_ENV
+ echo "AZURE_LOCATION=$VALID_REGION" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Generate Resource Group Name
+ id: generate_rg_name
+ shell: bash
+ env:
+ INPUT_RESOURCE_GROUP_NAME: ${{ inputs.resource_group_name }}
+ run: |
+ # Check if a resource group name was provided as input
+ if [[ -n "$INPUT_RESOURCE_GROUP_NAME" ]]; then
+ echo "Using provided Resource Group name: $INPUT_RESOURCE_GROUP_NAME"
+ echo "RESOURCE_GROUP_NAME=$INPUT_RESOURCE_GROUP_NAME" >> $GITHUB_ENV
+ else
+ echo "Generating a unique resource group name..."
+ ACCL_NAME="byocc" # Account name as specified
+ SHORT_UUID=$(uuidgen | cut -d'-' -f1)
+ UNIQUE_RG_NAME="arg-${ACCL_NAME}-${SHORT_UUID}"
+ echo "RESOURCE_GROUP_NAME=${UNIQUE_RG_NAME}" >> $GITHUB_ENV
+ echo "Generated RESOURCE_GROUP_NAME: ${UNIQUE_RG_NAME}"
+ fi
+
+ - name: Install Bicep CLI
+ shell: bash
+ run: az bicep install
+
+ - name: Check and Create Resource Group
+ id: check_create_rg
+ shell: bash
+ run: |
+ set -e
+ echo "๐ Checking if resource group '$RESOURCE_GROUP_NAME' exists..."
+ rg_exists=$(az group exists --name $RESOURCE_GROUP_NAME)
+ if [ "$rg_exists" = "false" ]; then
+ echo "๐ฆ Resource group does not exist. Creating new resource group '$RESOURCE_GROUP_NAME' in location '$AZURE_LOCATION'..."
+ az group create --name $RESOURCE_GROUP_NAME --location $AZURE_LOCATION || { echo "โ Error creating resource group"; exit 1; }
+ echo "โ
Resource group '$RESOURCE_GROUP_NAME' created successfully."
+ else
+ echo "โ
Resource group '$RESOURCE_GROUP_NAME' already exists. Deploying to existing resource group."
+ fi
+ echo "RESOURCE_GROUP_NAME=$RESOURCE_GROUP_NAME" >> $GITHUB_OUTPUT
+ echo "RESOURCE_GROUP_NAME=$RESOURCE_GROUP_NAME" >> $GITHUB_ENV
+
+ - name: Generate Unique Solution Prefix
+ id: generate_solution_prefix
+ shell: bash
+ run: |
+ set -e
+ COMMON_PART="psldg"
+ TIMESTAMP=$(date +%s)
+ UPDATED_TIMESTAMP=$(echo $TIMESTAMP | tail -c 6)
+ UNIQUE_SOLUTION_PREFIX="${COMMON_PART}${UPDATED_TIMESTAMP}"
+ echo "SOLUTION_PREFIX=${UNIQUE_SOLUTION_PREFIX}" >> $GITHUB_ENV
+ echo "Generated SOLUTION_PREFIX: ${UNIQUE_SOLUTION_PREFIX}"
+
+ - name: Determine Docker Image Tag
+ id: determine_image_tag
+ shell: bash
+ env:
+ INPUT_DOCKER_IMAGE_TAG: ${{ inputs.docker_image_tag }}
+ run: |
+ if [[ "${{ env.BUILD_DOCKER_IMAGE }}" == "true" ]]; then
+ if [[ -n "$INPUT_DOCKER_IMAGE_TAG" ]]; then
+ IMAGE_TAG="$INPUT_DOCKER_IMAGE_TAG"
+ echo "๐ Using Docker image tag from build job: $IMAGE_TAG"
+ else
+ echo "โ Docker build job failed or was skipped, but BUILD_DOCKER_IMAGE is true"
+ exit 1
+ fi
+ else
+ echo "๐ท๏ธ Using existing Docker image based on branch..."
+ BRANCH_NAME="${{ env.BRANCH_NAME }}"
+ echo "Current branch: $BRANCH_NAME"
+
+ if [[ "$BRANCH_NAME" == "main" ]]; then
+ IMAGE_TAG="latest"
+ elif [[ "$BRANCH_NAME" == "dev" ]]; then
+ IMAGE_TAG="dev"
+ elif [[ "$BRANCH_NAME" == "demo" ]]; then
+ IMAGE_TAG="demo"
+ else
+ IMAGE_TAG="latest"
+ echo "Using default for branch '$BRANCH_NAME' - image tag: latest"
+ fi
+ echo "Using existing Docker image tag: $IMAGE_TAG"
+ fi
+
+ echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV
+ echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_OUTPUT
+
+ - name: Generate Unique Environment Name
+ id: generate_env_name
+ shell: bash
+ run: |
+ COMMON_PART="pslc"
+ TIMESTAMP=$(date +%s)
+ UPDATED_TIMESTAMP=$(echo $TIMESTAMP | tail -c 6)
+ UNIQUE_ENV_NAME="${COMMON_PART}${UPDATED_TIMESTAMP}"
+ echo "ENV_NAME=${UNIQUE_ENV_NAME}" >> $GITHUB_ENV
+ echo "Generated Environment Name: ${UNIQUE_ENV_NAME}"
+ echo "ENV_NAME=${UNIQUE_ENV_NAME}" >> $GITHUB_OUTPUT
+
+ - name: Display Workflow Configuration to GitHub Summary
+ shell: bash
+ env:
+ INPUT_TRIGGER_TYPE: ${{ inputs.trigger_type }}
+ INPUT_AZURE_LOCATION: ${{ inputs.azure_location }}
+ INPUT_RESOURCE_GROUP_NAME: ${{ inputs.resource_group_name }}
+ run: |
+ echo "## ๐ Workflow Configuration Summary" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "| Configuration | Value |" >> $GITHUB_STEP_SUMMARY
+ echo "|---------------|-------|" >> $GITHUB_STEP_SUMMARY
+ echo "| **Branch** | \`${{ env.BRANCH_NAME }}\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| **WAF Enabled** | ${{ env.WAF_ENABLED == 'true' && 'โ
Yes' || 'โ No' }} |" >> $GITHUB_STEP_SUMMARY
+ echo "| **EXP Enabled** | ${{ env.EXP == 'true' && 'โ
Yes' || 'โ No' }} |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Run E2E Tests** | \`${{ env.RUN_E2E_TESTS }}\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Cleanup Resources** | ${{ env.CLEANUP_RESOURCES == 'true' && 'โ
Yes' || 'โ No' }} |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Build Docker Image** | ${{ env.BUILD_DOCKER_IMAGE == 'true' && 'โ
Yes' || 'โ No' }} |" >> $GITHUB_STEP_SUMMARY
+
+ if [[ "$INPUT_TRIGGER_TYPE" == "workflow_dispatch" && -n "$INPUT_AZURE_LOCATION" ]]; then
+ echo "| **Azure Location** | \`$INPUT_AZURE_LOCATION\` (User Selected) |" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ if [[ -n "$INPUT_RESOURCE_GROUP_NAME" ]]; then
+ echo "| **Resource Group** | \`$INPUT_RESOURCE_GROUP_NAME\` (Pre-specified) |" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "| **Resource Group** | \`${{ env.RESOURCE_GROUP_NAME }}\` (Auto-generated) |" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ if [[ "$INPUT_TRIGGER_TYPE" != "workflow_dispatch" ]]; then
+ echo "โน๏ธ **Note:** Automatic Trigger - Using Non-WAF + Non-EXP configuration" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "โน๏ธ **Note:** Manual Trigger - Using user-specified configuration" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ deploy-linux:
+ name: Deploy on Linux
+ needs: azure-setup
+ if: inputs.runner_os == 'ubuntu-latest' && !cancelled() && needs.azure-setup.result == 'success'
+ uses: ./.github/workflows/job-deploy-linux.yml
+ with:
+ ENV_NAME: ${{ needs.azure-setup.outputs.ENV_NAME }}
+ AZURE_ENV_OPENAI_LOCATION: ${{ needs.azure-setup.outputs.AZURE_ENV_OPENAI_LOCATION }}
+ AZURE_LOCATION: ${{ needs.azure-setup.outputs.AZURE_LOCATION }}
+ RESOURCE_GROUP_NAME: ${{ needs.azure-setup.outputs.RESOURCE_GROUP_NAME }}
+ IMAGE_TAG: ${{ needs.azure-setup.outputs.IMAGE_TAG }}
+ BUILD_DOCKER_IMAGE: ${{ inputs.build_docker_image || 'false' }}
+ EXP: ${{ inputs.EXP || 'false' }}
+ WAF_ENABLED: ${{ inputs.waf_enabled == true && 'true' || 'false' }}
+ AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}
+ AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
+ secrets: inherit
+
+ deploy-windows:
+ name: Deploy on Windows
+ needs: azure-setup
+ if: inputs.runner_os == 'windows-latest' && !cancelled() && needs.azure-setup.result == 'success'
+ uses: ./.github/workflows/job-deploy-windows.yml
+ with:
+ ENV_NAME: ${{ needs.azure-setup.outputs.ENV_NAME }}
+ AZURE_ENV_OPENAI_LOCATION: ${{ needs.azure-setup.outputs.AZURE_ENV_OPENAI_LOCATION }}
+ AZURE_LOCATION: ${{ needs.azure-setup.outputs.AZURE_LOCATION }}
+ RESOURCE_GROUP_NAME: ${{ needs.azure-setup.outputs.RESOURCE_GROUP_NAME }}
+ IMAGE_TAG: ${{ needs.azure-setup.outputs.IMAGE_TAG }}
+ BUILD_DOCKER_IMAGE: ${{ inputs.build_docker_image || 'false' }}
+ EXP: ${{ inputs.EXP || 'false' }}
+ WAF_ENABLED: ${{ inputs.waf_enabled == true && 'true' || 'false' }}
+ AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}
+ AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
+ secrets: inherit
diff --git a/.github/workflows/job-docker-build.yml b/.github/workflows/job-docker-build.yml
new file mode 100644
index 00000000..0cb58768
--- /dev/null
+++ b/.github/workflows/job-docker-build.yml
@@ -0,0 +1,135 @@
+name: Docker Build Job
+
+on:
+ workflow_call:
+ inputs:
+ trigger_type:
+ description: 'Trigger type (workflow_dispatch, pull_request, schedule)'
+ required: true
+ type: string
+ build_docker_image:
+ description: 'Build And Push Docker Image (Optional)'
+ required: false
+ default: false
+ type: boolean
+ outputs:
+ IMAGE_TAG:
+ description: "Generated Docker Image Tag"
+ value: ${{ jobs.docker-build.outputs.IMAGE_TAG }}
+
+env:
+ BRANCH_NAME: ${{ github.event.workflow_run.head_branch || github.head_ref || github.ref_name }}
+
+jobs:
+ docker-build:
+ if: inputs.trigger_type == 'workflow_dispatch' && inputs.build_docker_image == true
+ runs-on: ubuntu-latest
+ outputs:
+ IMAGE_TAG: ${{ steps.generate_docker_tag.outputs.IMAGE_TAG }}
+ steps:
+ - name: Checkout Code
+ uses: actions/checkout@v4
+
+ - name: Get current date
+ id: date
+ run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
+
+ - name: Determine Tag Name Based on Branch
+ id: determine_tag
+ run: |
+ BRANCH_NAME="${{ github.ref_name }}"
+ if [[ "$BRANCH_NAME" == "main" ]]; then
+ echo "tagname=latest_waf" >> $GITHUB_OUTPUT
+ elif [[ "$BRANCH_NAME" == "dev" ]]; then
+ echo "tagname=dev" >> $GITHUB_OUTPUT
+ elif [[ "$BRANCH_NAME" == "demo" ]]; then
+ echo "tagname=demo" >> $GITHUB_OUTPUT
+ elif [[ "$BRANCH_NAME" == "dependabotchanges" ]]; then
+ echo "tagname=dependabotchanges" >> $GITHUB_OUTPUT
+ elif [[ "$BRANCH_NAME" == "PSL-US-27776" ]]; then
+ echo "tagname=prerelease" >> $GITHUB_OUTPUT
+ else
+ # Clean branch name for use as Docker tag
+ CLEAN_BRANCH_NAME=$(echo "$BRANCH_NAME" | sed 's/[^a-zA-Z0-9._-]/-/g' | sed 's/--*/-/g' | sed 's/^-\|-$//g')
+ echo "tagname=$CLEAN_BRANCH_NAME" >> $GITHUB_OUTPUT
+ fi
+ - name: Generate Unique Docker Image Tag
+ id: generate_docker_tag
+ shell: bash
+ run: |
+ echo "๐จ Building new Docker image - generating unique tag..."
+ BRANCH_NAME="${{ github.head_ref || github.ref_name }}"
+ CLEAN_BRANCH_NAME=$(echo "$BRANCH_NAME" | sed 's/[^a-zA-Z0-9._-]/-/g' | sed 's/--*/-/g' | sed 's/^-\|-$//g')
+ UNIQUE_TAG="${CLEAN_BRANCH_NAME}-${{ steps.date.outputs.date }}-${{ github.run_number }}"
+ echo "IMAGE_TAG=$UNIQUE_TAG" >> $GITHUB_ENV
+ echo "IMAGE_TAG=$UNIQUE_TAG" >> $GITHUB_OUTPUT
+ echo "Generated unique Docker tag: $UNIQUE_TAG"
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to Azure Container Registry
+ uses: azure/docker-login@v2
+ with:
+ login-server: ${{ secrets.ACR_LOGIN_SERVER }}
+ username: ${{ secrets.ACR_USERNAME }}
+ password: ${{ secrets.ACR_PASSWORD }}
+
+ - name: Output ACR Login Server
+ run: |
+ echo "ACR Login Server: ${{ secrets.ACR_LOGIN_SERVER }}"
+ - name: Build and Push Docker Image for WebApp
+ uses: docker/build-push-action@v6
+ env:
+ DOCKER_BUILD_SUMMARY: false
+ with:
+ context: ./src/App
+ file: ./src/App/Dockerfile
+ push: true
+ tags: |
+ ${{ secrets.ACR_LOGIN_SERVER || 'acrlogin.azurecr.io' }}/frontend:${{ steps.determine_tag.outputs.tagname }}
+ ${{ secrets.ACR_LOGIN_SERVER || 'acrlogin.azurecr.io' }}/frontend:${{ steps.generate_docker_tag.outputs.IMAGE_TAG }}
+ - name: Build and Push Docker Image for API
+ uses: docker/build-push-action@v6
+ env:
+ DOCKER_BUILD_SUMMARY: false
+ with:
+ context: ./src/api
+ file: ./src/api/Dockerfile
+ push: true
+ tags: |
+ ${{ secrets.ACR_LOGIN_SERVER || 'acrlogin.azurecr.io' }}/backend:${{ steps.determine_tag.outputs.tagname }}
+ ${{ secrets.ACR_LOGIN_SERVER || 'acrlogin.azurecr.io' }}/backend:${{ steps.generate_docker_tag.outputs.IMAGE_TAG }}
+ - name: Verify Docker Image Build
+ shell: bash
+ run: |
+ echo "โ
Docker image successfully built and pushed"
+ echo "Image tag: ${{ steps.generate_docker_tag.outputs.IMAGE_TAG }}"
+
+ - name: Generate Docker Build Summary
+ if: always()
+ shell: bash
+ run: |
+ ACR_NAME=$(echo "${{ secrets.ACR_LOGIN_SERVER }}")
+ echo "## ๐ณ Docker Build Job Summary" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
+ echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
+ echo "| **Job Status** | ${{ job.status == 'success' && 'โ
Success' || 'โ Failed' }} |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Image Tag** | \`${{ steps.generate_docker_tag.outputs.IMAGE_TAG }}\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Branch Tag** | \`${{ steps.determine_tag.outputs.tagname }}\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Branch** | ${{ env.BRANCH_NAME }} |" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ if [[ "${{ job.status }}" == "success" ]]; then
+ echo "### โ
Build Details" >> $GITHUB_STEP_SUMMARY
+ echo "Successfully built and pushed two Docker images to ACR:" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "**Built Images:**" >> $GITHUB_STEP_SUMMARY
+ echo "- \`${ACR_NAME}/frontend:${{ steps.determine_tag.outputs.tagname }}\`" >> $GITHUB_STEP_SUMMARY
+ echo "- \`${ACR_NAME}/frontend:${{ steps.generate_docker_tag.outputs.IMAGE_TAG }}\`" >> $GITHUB_STEP_SUMMARY
+ echo "- \`${ACR_NAME}/backend:${{ steps.determine_tag.outputs.tagname }}\`" >> $GITHUB_STEP_SUMMARY
+ echo "- \`${ACR_NAME}/backend:${{ steps.generate_docker_tag.outputs.IMAGE_TAG }}\`" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "### โ Build Failed" >> $GITHUB_STEP_SUMMARY
+ echo "- Docker build process encountered an error" >> $GITHUB_STEP_SUMMARY
+ echo "- Check the docker-build job for detailed error information" >> $GITHUB_STEP_SUMMARY
+ fi
\ No newline at end of file
diff --git a/.github/workflows/job-send-notification.yml b/.github/workflows/job-send-notification.yml
new file mode 100644
index 00000000..c1f19df3
--- /dev/null
+++ b/.github/workflows/job-send-notification.yml
@@ -0,0 +1,431 @@
+name: Send Notification Job
+permissions:
+ contents: read
+ actions: read
+on:
+ workflow_call:
+ inputs:
+ trigger_type:
+ description: 'Trigger type (workflow_dispatch, pull_request, schedule)'
+ required: true
+ type: string
+ waf_enabled:
+ description: 'Enable WAF'
+ required: false
+ default: false
+ type: boolean
+ EXP:
+ description: 'Enable EXP'
+ required: false
+ default: false
+ type: boolean
+ run_e2e_tests:
+ description: 'Run End-to-End Tests'
+ required: false
+ default: 'GoldenPath-Testing'
+ type: string
+ existing_webapp_url:
+ description: 'Existing Container WebApp URL (Skips Deployment)'
+ required: false
+ default: ''
+ type: string
+ deploy_result:
+ description: 'Deploy job result (success, failure, skipped)'
+ required: true
+ type: string
+ e2e_test_result:
+ description: 'E2E test job result (success, failure, skipped)'
+ required: true
+ type: string
+ CONTAINER_WEB_APPURL:
+ description: 'Container Web App URL'
+ required: false
+ default: ''
+ type: string
+ RESOURCE_GROUP_NAME:
+ description: 'Resource Group Name'
+ required: false
+ default: ''
+ type: string
+ QUOTA_FAILED:
+ description: 'Quota Check Failed Flag'
+ required: false
+ default: 'false'
+ type: string
+ TEST_SUCCESS:
+ description: 'Test Success Flag'
+ required: false
+ default: ''
+ type: string
+ TEST_REPORT_URL:
+ description: 'Test Report URL'
+ required: false
+ default: ''
+ type: string
+
+env:
+ GPT_MIN_CAPACITY: 100
+ BRANCH_NAME: ${{ github.event.workflow_run.head_branch || github.head_ref || github.ref_name }}
+ WAF_ENABLED: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.waf_enabled || false) || false }}
+ EXP: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.EXP || false) || false }}
+ RUN_E2E_TESTS: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.run_e2e_tests || 'GoldenPath-Testing') || 'GoldenPath-Testing' }}
+
+jobs:
+ send-notification:
+ runs-on: ubuntu-latest
+ continue-on-error: true
+ env:
+ accelerator_name: "customer-chatbot-solution-accelerator"
+ steps:
+ - name: Validate Workflow Input Parameters
+ shell: bash
+ env:
+ INPUT_TRIGGER_TYPE: ${{ inputs.trigger_type }}
+ INPUT_WAF_ENABLED: ${{ inputs.waf_enabled }}
+ INPUT_EXP: ${{ inputs.EXP }}
+ INPUT_RUN_E2E_TESTS: ${{ inputs.run_e2e_tests }}
+ INPUT_EXISTING_WEBAPP_URL: ${{ inputs.existing_webapp_url }}
+ INPUT_DEPLOY_RESULT: ${{ inputs.deploy_result }}
+ INPUT_E2E_TEST_RESULT: ${{ inputs.e2e_test_result }}
+ INPUT_CONTAINER_WEB_APPURL: ${{ inputs.CONTAINER_WEB_APPURL }}
+ INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ INPUT_QUOTA_FAILED: ${{ inputs.QUOTA_FAILED }}
+ INPUT_TEST_SUCCESS: ${{ inputs.TEST_SUCCESS }}
+ INPUT_TEST_REPORT_URL: ${{ inputs.TEST_REPORT_URL }}
+ run: |
+ echo "๐ Validating workflow input parameters..."
+ VALIDATION_FAILED=false
+
+ # Validate trigger_type (required - alphanumeric with underscores)
+ if [[ -z "$INPUT_TRIGGER_TYPE" ]]; then
+ echo "โ ERROR: trigger_type is required but was not provided"
+ VALIDATION_FAILED=true
+ elif [[ ! "$INPUT_TRIGGER_TYPE" =~ ^[a-zA-Z0-9_]+$ ]]; then
+ echo "โ ERROR: trigger_type '$INPUT_TRIGGER_TYPE' is invalid. Must contain only alphanumeric characters and underscores"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
trigger_type: '$INPUT_TRIGGER_TYPE' is valid"
+ fi
+
+ # Validate waf_enabled (boolean)
+ if [[ "$INPUT_WAF_ENABLED" != "true" && "$INPUT_WAF_ENABLED" != "false" ]]; then
+ echo "โ ERROR: waf_enabled must be 'true' or 'false', got: '$INPUT_WAF_ENABLED'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
waf_enabled: '$INPUT_WAF_ENABLED' is valid"
+ fi
+
+ # Validate EXP (boolean)
+ if [[ "$INPUT_EXP" != "true" && "$INPUT_EXP" != "false" ]]; then
+ echo "โ ERROR: EXP must be 'true' or 'false', got: '$INPUT_EXP'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
EXP: '$INPUT_EXP' is valid"
+ fi
+
+ # Validate run_e2e_tests (specific allowed values)
+ if [[ -n "$INPUT_RUN_E2E_TESTS" ]]; then
+ ALLOWED_VALUES=("None" "GoldenPath-Testing" "Smoke-Testing")
+ if [[ ! " ${ALLOWED_VALUES[@]} " =~ " ${INPUT_RUN_E2E_TESTS} " ]]; then
+ echo "โ ERROR: run_e2e_tests '$INPUT_RUN_E2E_TESTS' is invalid. Allowed values: ${ALLOWED_VALUES[*]}"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
run_e2e_tests: '$INPUT_RUN_E2E_TESTS' is valid"
+ fi
+ fi
+
+ # Validate existing_webapp_url (must start with https if provided)
+ if [[ -n "$INPUT_EXISTING_WEBAPP_URL" ]]; then
+ if [[ ! "$INPUT_EXISTING_WEBAPP_URL" =~ ^https:// ]]; then
+ echo "โ ERROR: existing_webapp_url must start with 'https://', got: '$INPUT_EXISTING_WEBAPP_URL'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
existing_webapp_url: '$INPUT_EXISTING_WEBAPP_URL' is valid"
+ fi
+ fi
+
+ # Validate deploy_result (required, must be specific values)
+ if [[ -z "$INPUT_DEPLOY_RESULT" ]]; then
+ echo "โ ERROR: deploy_result is required but not provided"
+ VALIDATION_FAILED=true
+ else
+ ALLOWED_DEPLOY_RESULTS=("success" "failure" "skipped")
+ if [[ ! " ${ALLOWED_DEPLOY_RESULTS[@]} " =~ " ${INPUT_DEPLOY_RESULT} " ]]; then
+ echo "โ ERROR: deploy_result '$INPUT_DEPLOY_RESULT' is invalid. Allowed values: ${ALLOWED_DEPLOY_RESULTS[*]}"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
deploy_result: '$INPUT_DEPLOY_RESULT' is valid"
+ fi
+ fi
+
+ # Validate e2e_test_result (required, must be specific values)
+ if [[ -z "$INPUT_E2E_TEST_RESULT" ]]; then
+ echo "โ ERROR: e2e_test_result is required but not provided"
+ VALIDATION_FAILED=true
+ else
+ ALLOWED_TEST_RESULTS=("success" "failure" "skipped")
+ if [[ ! " ${ALLOWED_TEST_RESULTS[@]} " =~ " ${INPUT_E2E_TEST_RESULT} " ]]; then
+ echo "โ ERROR: e2e_test_result '$INPUT_E2E_TEST_RESULT' is invalid. Allowed values: ${ALLOWED_TEST_RESULTS[*]}"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
e2e_test_result: '$INPUT_E2E_TEST_RESULT' is valid"
+ fi
+ fi
+
+ # Validate CONTAINER_WEB_APPURL (must start with https if provided)
+ if [[ -n "$INPUT_CONTAINER_WEB_APPURL" ]]; then
+ if [[ ! "$INPUT_CONTAINER_WEB_APPURL" =~ ^https:// ]]; then
+ echo "โ ERROR: CONTAINER_WEB_APPURL must start with 'https://', got: '$INPUT_CONTAINER_WEB_APPURL'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
CONTAINER_WEB_APPURL: '$INPUT_CONTAINER_WEB_APPURL' is valid"
+ fi
+ fi
+
+ # Validate RESOURCE_GROUP_NAME (Azure resource group naming convention if provided)
+ if [[ -n "$INPUT_RESOURCE_GROUP_NAME" ]]; then
+ if [[ ! "$INPUT_RESOURCE_GROUP_NAME" =~ ^[a-zA-Z0-9._\(\)-]+$ ]] || [[ "$INPUT_RESOURCE_GROUP_NAME" =~ \.$ ]]; then
+ echo "โ ERROR: RESOURCE_GROUP_NAME '$INPUT_RESOURCE_GROUP_NAME' is invalid. Must contain only alphanumerics, periods, underscores, hyphens, and parentheses. Cannot end with period."
+ VALIDATION_FAILED=true
+ elif [[ ${#INPUT_RESOURCE_GROUP_NAME} -gt 90 ]]; then
+ echo "โ ERROR: RESOURCE_GROUP_NAME '$INPUT_RESOURCE_GROUP_NAME' exceeds 90 characters"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
RESOURCE_GROUP_NAME: '$INPUT_RESOURCE_GROUP_NAME' is valid"
+ fi
+ fi
+
+ # Validate QUOTA_FAILED (must be 'true' or 'false')
+ if [[ "$INPUT_QUOTA_FAILED" != "true" && "$INPUT_QUOTA_FAILED" != "false" ]]; then
+ echo "โ ERROR: QUOTA_FAILED must be 'true' or 'false', got: '$INPUT_QUOTA_FAILED'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
QUOTA_FAILED: '$INPUT_QUOTA_FAILED' is valid"
+ fi
+
+ # Validate TEST_SUCCESS (must be 'true' or 'false' or empty)
+ if [[ -n "$INPUT_TEST_SUCCESS" ]]; then
+ if [[ "$INPUT_TEST_SUCCESS" != "true" && "$INPUT_TEST_SUCCESS" != "false" ]]; then
+ echo "โ ERROR: TEST_SUCCESS must be 'true', 'false', or empty, got: '$INPUT_TEST_SUCCESS'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
TEST_SUCCESS: '$INPUT_TEST_SUCCESS' is valid"
+ fi
+ fi
+
+ # Validate TEST_REPORT_URL (must start with https if provided)
+ if [[ -n "$INPUT_TEST_REPORT_URL" ]]; then
+ if [[ ! "$INPUT_TEST_REPORT_URL" =~ ^https:// ]]; then
+ echo "โ ERROR: TEST_REPORT_URL must start with 'https://', got: '$INPUT_TEST_REPORT_URL'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
TEST_REPORT_URL: '$INPUT_TEST_REPORT_URL' is valid"
+ fi
+ fi
+
+ # Fail workflow if any validation failed
+ if [[ "$VALIDATION_FAILED" == "true" ]]; then
+ echo ""
+ echo "โ Parameter validation failed. Please correct the errors above and try again."
+ exit 1
+ fi
+
+ echo ""
+ echo "โ
All input parameters validated successfully!"
+
+ - name: Determine Test Suite Display Name
+ id: test_suite
+ shell: bash
+ env:
+ RUN_E2E_TESTS: ${{ env.RUN_E2E_TESTS }}
+ run: |
+ if [ "$RUN_E2E_TESTS" = "GoldenPath-Testing" ]; then
+ TEST_SUITE_NAME="Golden Path Testing"
+ elif [ "$RUN_E2E_TESTS" = "Smoke-Testing" ]; then
+ TEST_SUITE_NAME="Smoke Testing"
+ elif [ "$RUN_E2E_TESTS" = "None" ]; then
+ TEST_SUITE_NAME="None"
+ else
+ TEST_SUITE_NAME="$RUN_E2E_TESTS"
+ fi
+ echo "TEST_SUITE_NAME=$TEST_SUITE_NAME" >> $GITHUB_OUTPUT
+ echo "Test Suite: $TEST_SUITE_NAME"
+
+ - name: Send Quota Failure Notification
+ if: inputs.deploy_result == 'failure' && inputs.QUOTA_FAILED == 'true'
+ shell: bash
+ env:
+ GITHUB_REPOSITORY: ${{ github.repository }}
+ GITHUB_RUN_ID: ${{ github.run_id }}
+ ACCELERATOR_NAME: ${{ env.accelerator_name }}
+ LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}
+ run: |
+ RUN_URL="https://github.com/${GITHUB_REPOSITORY }/actions/runs/${GITHUB_RUN_ID}"
+ EMAIL_BODY=$(cat <Dear Team,
We would like to inform you that the ${ACCELERATOR_NAME} deployment has failed due to insufficient quota in the requested regions.
Issue Details:
โข Quota check failed for GPT model
โข Required GPT Capacity: ${{ env.GPT_MIN_CAPACITY }}
โข Checked Regions: ${{ vars.AZURE_REGIONS }}
Run URL: ${RUN_URL}
Please resolve the quota issue and retry the deployment.
Best regards,
Your Automation Team
",
+ "subject": "${ACCELERATOR_NAME} Pipeline - Failed (Insufficient Quota)"
+ }
+ EOF
+ )
+
+ curl -X POST "${LOGICAPP_URL}" \
+ -H "Content-Type: application/json" \
+ -d "$EMAIL_BODY" || echo "Failed to send quota failure notification"
+
+ - name: Send Deployment Failure Notification
+ if: inputs.deploy_result == 'failure' && inputs.QUOTA_FAILED != 'true'
+ shell: bash
+ env:
+ INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ ACCELERATOR_NAME: ${{ env.accelerator_name }}
+ LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}
+ WAF_ENABLED: ${{ env.WAF_ENABLED }}
+ EXP: ${{ env.EXP }}
+ run: |
+ RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
+ RESOURCE_GROUP="$INPUT_RESOURCE_GROUP_NAME"
+
+ EMAIL_BODY=$(cat <Dear Team,We would like to inform you that the ${ACCELERATOR_NAME} deployment process has encountered an issue and has failed to complete successfully.
Deployment Details:
โข Resource Group: ${RESOURCE_GROUP}
โข WAF Enabled: ${WAF_ENABLED}
โข EXP Enabled: ${EXP}
Run URL: ${RUN_URL}
Please investigate the deployment failure at your earliest convenience.
Best regards,
Your Automation Team
",
+ "subject": "${ACCELERATOR_NAME} Pipeline - Failed"
+ }
+ EOF
+ )
+
+ curl -X POST "${LOGICAPP_URL}" \
+ -H "Content-Type: application/json" \
+ -d "$EMAIL_BODY" || echo "Failed to send deployment failure notification"
+
+ - name: Send Success Notification
+ if: inputs.deploy_result == 'success' && (inputs.e2e_test_result == 'skipped' || inputs.TEST_SUCCESS == 'true')
+ shell: bash
+ env:
+ INPUT_CONTAINER_WEB_APPURL: ${{ inputs.CONTAINER_WEB_APPURL }}
+ INPUT_EXISTING_WEBAPP_URL: ${{ inputs.existing_webapp_url }}
+ INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ INPUT_TEST_REPORT_URL: ${{ inputs.TEST_REPORT_URL }}
+ INPUT_E2E_TEST_RESULT: ${{ inputs.e2e_test_result }}
+ ACCELERATOR_NAME: ${{ env.accelerator_name }}
+ LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}
+ GITHUB_REPOSITORY: ${{ github.repository }}
+ GITHUB_RUN_ID: ${{ github.run_id }}
+ EXP: ${{ env.EXP }}
+ WAF_ENABLED: ${{ env.WAF_ENABLED }}
+
+ run: |
+ RUN_URL="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
+ WEBAPP_URL="${INPUT_CONTAINER_WEB_APPURL:-$INPUT_EXISTING_WEBAPP_URL}"
+ RESOURCE_GROUP="$INPUT_RESOURCE_GROUP_NAME"
+ TEST_REPORT_URL="$INPUT_TEST_REPORT_URL"
+ TEST_SUITE_NAME="${{ steps.test_suite.outputs.TEST_SUITE_NAME }}"
+
+ if [ "$INPUT_E2E_TEST_RESULT" = "skipped" ]; then
+ EMAIL_BODY=$(cat <Dear Team,We would like to inform you that the ${ACCELERATOR_NAME} deployment has completed successfully.
Deployment Details:
โข Resource Group: ${RESOURCE_GROUP}
โข Web App URL: ${WEBAPP_URL}
โข E2E Tests: Skipped (as configured)
Configuration:
โข WAF Enabled: ${WAF_ENABLED}
โข EXP Enabled: ${EXP}
Run URL: ${RUN_URL}
Best regards,
Your Automation Team
",
+ "subject": "${ACCELERATOR_NAME} Pipeline - Deployment Success"
+ }
+ EOF
+ )
+ else
+ EMAIL_BODY=$(cat <Dear Team,We would like to inform you that the ${ACCELERATOR_NAME} deployment and testing process has completed successfully.
Deployment Details:
โข Resource Group: ${RESOURCE_GROUP}
โข Web App URL: ${WEBAPP_URL}
โข E2E Tests: Passed โ
โข Test Suite: ${TEST_SUITE_NAME}
โข Test Report: View Report
Configuration:
โข WAF Enabled: ${WAF_ENABLED}
โข EXP Enabled: ${EXP}
Run URL: ${RUN_URL}
Best regards,
Your Automation Team
",
+ "subject": "${ACCELERATOR_NAME} Pipeline - Test Automation - Success"
+ }
+ EOF
+ )
+ fi
+
+ curl -X POST "${LOGICAPP_URL}" \
+ -H "Content-Type: application/json" \
+ -d "$EMAIL_BODY" || echo "Failed to send success notification"
+
+ - name: Send Test Failure Notification
+ if: inputs.deploy_result == 'success' && inputs.e2e_test_result != 'skipped' && inputs.TEST_SUCCESS != 'true'
+ shell: bash
+ env:
+ GITHUB_REPOSITORY: ${{ github.repository }}
+ GITHUB_RUN_ID: ${{ github.run_id }}
+ INPUT_CONTAINER_WEB_APPURL: ${{ inputs.CONTAINER_WEB_APPURL }}
+ INPUT_EXISTING_WEBAPP_URL: ${{ inputs.existing_webapp_url }}
+ INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ ACCELERATOR_NAME: ${{ env.accelerator_name }}
+ LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}
+ INPUT_TEST_REPORT_URL: ${{ inputs.TEST_REPORT_URL }}
+ run: |
+ RUN_URL="https://github.com/${{ env.GITHUB_REPOSITORY }}/actions/runs/${{ env.GITHUB_RUN_ID }}"
+ TEST_REPORT_URL="$INPUT_TEST_REPORT_URL"
+ WEBAPP_URL="${INPUT_CONTAINER_WEB_APPURL:-$INPUT_EXISTING_WEBAPP_URL}"
+ RESOURCE_GROUP="$INPUT_RESOURCE_GROUP_NAME"
+ TEST_SUITE_NAME="${{ steps.test_suite.outputs.TEST_SUITE_NAME }}"
+
+ EMAIL_BODY=$(cat <Dear Team,We would like to inform you that ${ACCELERATOR_NAME} accelerator test automation process has encountered issues and failed to complete successfully.
Deployment Details:
โข Resource Group: ${RESOURCE_GROUP}
โข Web App URL: ${WEBAPP_URL}
โข Deployment Status: โ
Success
โข E2E Tests: โ Failed
โข Test Suite: ${TEST_SUITE_NAME}
Test Details:
โข Test Report: View Report
Run URL: ${RUN_URL}
Please investigate the matter at your earliest convenience.
Best regards,
Your Automation Team
",
+ "subject": "${ACCELERATOR_NAME} Pipeline - Test Automation - Failed"
+ }
+ EOF
+ )
+
+ curl -X POST "${LOGICAPP_URL}" \
+ -H "Content-Type: application/json" \
+ -d "$EMAIL_BODY" || echo "Failed to send test failure notification"
+
+ - name: Send Existing URL Success Notification
+ if: inputs.deploy_result == 'skipped' && inputs.existing_webapp_url != '' && inputs.e2e_test_result == 'success' && (inputs.TEST_SUCCESS == 'true' || inputs.TEST_SUCCESS == '')
+ shell: bash
+ env:
+ GITHUB_REPOSITORY: ${{ github.repository }}
+ GITHUB_RUN_ID: ${{ github.run_id }}
+ INPUT_EXISTING_WEBAPP_URL: ${{ inputs.existing_webapp_url }}
+ INPUT_TEST_REPORT_URL: ${{ inputs.TEST_REPORT_URL }}
+ ACCELERATOR_NAME: ${{ env.accelerator_name }}
+ LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}
+ run: |
+ RUN_URL="https://github.com/${{ env.GITHUB_REPOSITORY }}/actions/runs/${{ env.GITHUB_RUN_ID }}"
+ EXISTING_URL="$INPUT_EXISTING_WEBAPP_URL"
+ TEST_REPORT_URL="$INPUT_TEST_REPORT_URL"
+ TEST_SUITE_NAME="${{ steps.test_suite.outputs.TEST_SUITE_NAME }}"
+
+ EMAIL_BODY=$(cat <Dear Team,The ${ACCELERATOR_NAME} pipeline executed against the specified Target URL and testing process has completed successfully.
Test Results:
โข Status: โ
Passed
โข Test Suite: ${TEST_SUITE_NAME}
${TEST_REPORT_URL:+โข Test Report: View Report}
โข Target URL: ${EXISTING_URL}
Deployment: Skipped
Run URL: ${RUN_URL}
Best regards,
Your Automation Team
",
+ "subject": "${ACCELERATOR_NAME} Pipeline - Test Automation Passed "
+ }
+ EOF
+ )
+
+ curl -X POST "${LOGICAPP_URL}" \
+ -H "Content-Type: application/json" \
+ -d "$EMAIL_BODY" || echo "Failed to send existing URL success notification"
+
+ - name: Send Existing URL Test Failure Notification
+ if: inputs.deploy_result == 'skipped' && inputs.existing_webapp_url != '' && inputs.e2e_test_result == 'failure'
+ shell: bash
+ env:
+ GITHUB_REPOSITORY: ${{ github.repository }}
+ GITHUB_RUN_ID: ${{ github.run_id }}
+ INPUT_EXISTING_WEBAPP_URL: ${{ inputs.existing_webapp_url }}
+ INPUT_TEST_REPORT_URL: ${{ inputs.TEST_REPORT_URL }}
+ ACCELERATOR_NAME: ${{ env.accelerator_name }}
+ LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}
+ run: |
+ RUN_URL="https://github.com/${{ env.GITHUB_REPOSITORY }}/actions/runs/${{ env.GITHUB_RUN_ID }}"
+ EXISTING_URL="$INPUT_EXISTING_WEBAPP_URL"
+ TEST_REPORT_URL="$INPUT_TEST_REPORT_URL"
+ TEST_SUITE_NAME="${{ steps.test_suite.outputs.TEST_SUITE_NAME }}"
+
+ EMAIL_BODY=$(cat <Dear Team,The ${ACCELERATOR_NAME} pipeline executed against the specified Target URL and the test automation has encountered issues and failed to complete successfully.
Failure Details:
โข Target URL: ${EXISTING_URL}
${TEST_REPORT_URL:+โข Test Report: View Report}
โข Test Suite: ${TEST_SUITE_NAME}
โข Deployment: Skipped
Run URL: ${RUN_URL}
Best regards,
Your Automation Team
",
+ "subject": "${ACCELERATOR_NAME} Pipeline - Test Automation Failed "
+ }
+ EOF
+ )
+
+ curl -X POST "${LOGICAPP_URL}" \
+ -H "Content-Type: application/json" \
+ -d "$EMAIL_BODY" || echo "Failed to send existing URL test failure notification"
diff --git a/.github/workflows/test-automation-v2.yml b/.github/workflows/test-automation-v2.yml
new file mode 100644
index 00000000..625f6e2f
--- /dev/null
+++ b/.github/workflows/test-automation-v2.yml
@@ -0,0 +1,196 @@
+name: Test Automation Code Modernization - v2
+
+permissions:
+ contents: read
+ actions: read
+on:
+ workflow_call:
+ inputs:
+ TEST_URL:
+ required: true
+ type: string
+ description: "Web URL for code modernization"
+ TEST_SUITE:
+ required: false
+ type: string
+ default: "GoldenPath-Testing"
+ description: "Test suite to run: 'Smoke-Testing', 'GoldenPath-Testing' "
+ outputs:
+ TEST_SUCCESS:
+ description: "Whether tests passed"
+ value: ${{ jobs.test.outputs.TEST_SUCCESS }}
+ TEST_REPORT_URL:
+ description: "URL to test report artifact"
+ value: ${{ jobs.test.outputs.TEST_REPORT_URL }}
+
+env:
+ url: ${{ inputs.TEST_URL }}
+ accelerator_name: "Code Modernization"
+ test_suite: ${{ inputs.TEST_SUITE }}
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ outputs:
+ TEST_SUCCESS: ${{ steps.test1.outcome == 'success' || steps.test2.outcome == 'success' || steps.test3.outcome == 'success' }}
+ TEST_REPORT_URL: ${{ steps.upload_report.outputs.artifact-url }}
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v5
+
+ - name: Set up Python
+ uses: actions/setup-python@v6
+ with:
+ python-version: '3.13'
+
+ - name: Login to Azure
+ run: |
+ az login --service-principal -u ${{ secrets.AZURE_CLIENT_ID }} -p ${{ secrets.AZURE_CLIENT_SECRET }} --tenant ${{ secrets.AZURE_TENANT_ID }}
+ az account set --subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }}
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r tests/e2e-test/requirements.txt
+
+ - name: Ensure browsers are installed
+ run: python -m playwright install --with-deps chromium
+
+ - name: Validate URL
+ run: |
+ if [ -z "${{ env.url }}" ]; then
+ echo "ERROR: No URL provided for testing"
+ exit 1
+ fi
+ echo "Testing URL: ${{ env.url }}"
+ echo "Test Suite: ${{ env.test_suite }}"
+
+
+ - name: Wait for Application to be Ready
+ run: |
+ echo "Waiting for application to be ready at ${{ env.url }} "
+ max_attempts=10
+ attempt=1
+
+ while [ $attempt -le $max_attempts ]; do
+ echo "Attempt $attempt: Checking if application is ready..."
+ if curl -f -s "${{ env.url }}" > /dev/null; then
+ echo "Application is ready!"
+ break
+
+ fi
+
+ if [ $attempt -eq $max_attempts ]; then
+ echo "Application is not ready after $max_attempts attempts"
+ exit 1
+ fi
+
+ echo "Application not ready, waiting 30 seconds..."
+ sleep 30
+ attempt=$((attempt + 1))
+ done
+
+ - name: Run tests(1)
+ id: test1
+ run: |
+ if [ "${{ env.test_suite }}" == "GoldenPath-Testing" ]; then
+ xvfb-run pytest -m gp --html=report/report.html --self-contained-html
+ else
+ xvfb-run pytest --html=report/report.html --self-contained-html
+ fi
+ working-directory: tests/e2e-test
+ continue-on-error: true
+
+ - name: Sleep for 30 seconds
+ if: ${{ steps.test1.outcome == 'failure' }}
+ run: sleep 30s
+ shell: bash
+
+ - name: Run tests(2)
+ id: test2
+ if: ${{ steps.test1.outcome == 'failure' }}
+ run: |
+ if [ "${{ env.test_suite }}" == "GoldenPath-Testing" ]; then
+ xvfb-run pytest -m gp --html=report/report.html --self-contained-html
+ else
+ xvfb-run pytest --html=report/report.html --self-contained-html
+ fi
+ working-directory: tests/e2e-test
+ continue-on-error: true
+
+ - name: Sleep for 60 seconds
+ if: ${{ steps.test2.outcome == 'failure' }}
+ run: sleep 60s
+ shell: bash
+
+ - name: Run tests(3)
+ id: test3
+ if: ${{ steps.test2.outcome == 'failure' }}
+ run: |
+ if [ "${{ env.test_suite }}" == "GoldenPath-Testing" ]; then
+ xvfb-run pytest -m gp --html=report/report.html --self-contained-html
+ else
+ xvfb-run pytest --html=report/report.html --self-contained-html
+ fi
+ working-directory: tests/e2e-test
+
+ - name: Upload test report
+ id: upload_report
+ uses: actions/upload-artifact@v4
+ if: ${{ !cancelled() }}
+ with:
+ name: test-report
+ path: |
+ tests/e2e-test/report/*
+ tests/e2e-test/screenshots/*
+
+ - name: Generate E2E Test Summary
+ if: always()
+ run: |
+ # Determine test suite type for title
+ if [ "${{ env.test_suite }}" == "GoldenPath-Testing" ]; then
+ echo "## ๐งช E2E Test Job Summary : Golden Path Testing" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "## ๐งช E2E Test Job Summary : Smoke Testing" >> $GITHUB_STEP_SUMMARY
+ fi
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
+ echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
+
+ # Determine overall test result
+ OVERALL_SUCCESS="${{ steps.test1.outcome == 'success' || steps.test2.outcome == 'success' || steps.test3.outcome == 'success' }}"
+ if [[ "$OVERALL_SUCCESS" == "true" ]]; then
+ echo "| **Job Status** | โ
Success |" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "| **Job Status** | โ Failed |" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ echo "| **Target URL** | [${{ env.url }}](${{ env.url }}) |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Test Suite** | \`${{ env.test_suite }}\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Test Report** | [Download Artifact](${{ steps.upload_report.outputs.artifact-url }}) |" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ echo "### ๐ Test Execution Details" >> $GITHUB_STEP_SUMMARY
+ echo "| Attempt | Status | Notes |" >> $GITHUB_STEP_SUMMARY
+ echo "|---------|--------|-------|" >> $GITHUB_STEP_SUMMARY
+ echo "| **Test Run 1** | ${{ steps.test1.outcome == 'success' && 'โ
Passed' || 'โ Failed' }} | Initial test execution |" >> $GITHUB_STEP_SUMMARY
+
+ if [[ "${{ steps.test1.outcome }}" == "failure" ]]; then
+ echo "| **Test Run 2** | ${{ steps.test2.outcome == 'success' && 'โ
Passed' || steps.test2.outcome == 'failure' && 'โ Failed' || 'โธ๏ธ Skipped' }} | Retry after 30s delay |" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ if [[ "${{ steps.test2.outcome }}" == "failure" ]]; then
+ echo "| **Test Run 3** | ${{ steps.test3.outcome == 'success' && 'โ
Passed' || steps.test3.outcome == 'failure' && 'โ Failed' || 'โธ๏ธ Skipped' }} | Final retry after 60s delay |" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ if [[ "$OVERALL_SUCCESS" == "true" ]]; then
+ echo "### โ
Test Results" >> $GITHUB_STEP_SUMMARY
+ echo "- End-to-end tests completed successfully" >> $GITHUB_STEP_SUMMARY
+ echo "- Application is functioning as expected" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "### โ Test Results" >> $GITHUB_STEP_SUMMARY
+ echo "- All test attempts failed" >> $GITHUB_STEP_SUMMARY
+ echo "- Check the e2e-test/test job for detailed error information" >> $GITHUB_STEP_SUMMARY
+ fi
diff --git a/infra/scripts/agent_scripts/run_create_agents_scripts.sh b/infra/scripts/agent_scripts/run_create_agents_scripts.sh
index 0503c7a7..4c720c34 100644
--- a/infra/scripts/agent_scripts/run_create_agents_scripts.sh
+++ b/infra/scripts/agent_scripts/run_create_agents_scripts.sh
@@ -11,8 +11,6 @@ apiAppName="$5"
resourceGroup="$6"
searchEndpoint="$7"
-original_foundry_public_access=""
-
# get parameters from azd env, if not provided
if [ -z "$projectEndpoint" ]; then
projectEndpoint=$(azd env get-value AZURE_AI_AGENT_ENDPOINT)
@@ -42,111 +40,6 @@ if [ -z "$searchEndpoint" ]; then
searchEndpoint=$(azd env get-value AZURE_AI_SEARCH_ENDPOINT)
fi
-# Function to enable public network access temporarily
-enable_public_access() {
- echo "=== Temporarily enabling public network access for services ==="
- # Enable public access for AI Foundry
- # Extract the account resource ID (remove /projects/... part if present)
- aif_account_resource_id=$(echo "$aiFoundryResourceId" | sed 's|/projects/.*||')
- aif_resource_name=$(basename "$aif_account_resource_id")
- # Extract resource group from the AI Foundry account resource ID
- aif_resource_group=$(echo "$aif_account_resource_id" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
- # Extract subscription ID from the AI Foundry account resource ID
- aif_subscription_id=$(echo "$aif_account_resource_id" | sed -n 's|.*/subscriptions/\([^/]*\)/.*|\1|p')
-
- original_foundry_public_access=$(az cognitiveservices account show \
- --name "$aif_resource_name" \
- --resource-group "$aif_resource_group" \
- --subscription "$aif_subscription_id" \
- --query "properties.publicNetworkAccess" \
- --output tsv)
- if [ -z "$original_foundry_public_access" ] || [ "$original_foundry_public_access" = "null" ]; then
- echo "โ Info: Could not retrieve AI Foundry network access status."
- echo " AI Foundry network access might be managed differently."
- elif [ "$original_foundry_public_access" != "Enabled" ]; then
- echo "Current AI Foundry public access: $original_foundry_public_access"
- echo "Enabling public access for AI Foundry resource: $aif_resource_name (Resource Group: $aif_resource_group)"
- if MSYS_NO_PATHCONV=1 az resource update \
- --ids "$aif_account_resource_id" \
- --api-version 2024-10-01 \
- --set properties.publicNetworkAccess=Enabled properties.apiProperties="{}" \
- --output none; then
- echo "โ AI Foundry public access enabled"
- else
- echo "โ Warning: Failed to enable AI Foundry public access automatically."
- fi
- else
- echo "โ AI Foundry public access already enabled - no changes needed"
- fi
-
- # Wait for changes to take effect - Azure network changes can take 30-60 seconds to propagate
- echo "Waiting for network access changes to propagate (this may take up to 60 seconds)..."
- sleep 30
-
- # Verify that public access is actually enabled by checking the current state
- echo "Verifying public network access is enabled..."
- current_access=$(az cognitiveservices account show \
- --name "$aif_resource_name" \
- --resource-group "$aif_resource_group" \
- --subscription "$aif_subscription_id" \
- --query "properties.publicNetworkAccess" \
- --output tsv 2>/dev/null || echo "Unknown")
-
- if [ "$current_access" = "Enabled" ]; then
- echo "โ Verified: Public network access is enabled"
- else
- echo "โ Warning: Public access verification returned: $current_access"
- echo " Waiting additional 30 seconds for propagation..."
- sleep 30
- fi
-
- echo "=== Public network access enabled successfully ==="
- return 0
-}
-
-# Function to restore original network access settings
-restore_network_access() {
- echo "=== Restoring original network access settings ==="
-
- # Restore AI Foundry access only if it was changed from the original state
- if [ -n "$original_foundry_public_access" ] && [ "$original_foundry_public_access" != "Enabled" ]; then
- echo "Restoring AI Foundry public access to: $original_foundry_public_access"
- # Reconstruct the AI Foundry resource ID for restoration
- aif_account_resource_id=$(echo "$aiFoundryResourceId" | sed 's|/projects/.*||')
- # Try using the working approach to restore the original setting
- if MSYS_NO_PATHCONV=1 az resource update \
- --ids "$aif_account_resource_id" \
- --api-version 2024-10-01 \
- --set properties.publicNetworkAccess="$original_foundry_public_access" \
- --set properties.apiProperties.qnaAzureSearchEndpointKey="" \
- --set properties.networkAcls.bypass="AzureServices" \
- --output none 2>/dev/null; then
- echo "โ AI Foundry access restored"
- else
- echo "โ Warning: Failed to restore AI Foundry access automatically."
- echo " Please manually restore network access in the Azure portal if needed."
- fi
- else
- echo "AI Foundry access unchanged (no restoration needed)"
- fi
-
- echo "=== Network access restoration completed ==="
-}
-
-# Function to handle script cleanup on exit
-cleanup_on_exit() {
- exit_code=$?
- echo ""
- if [ $exit_code -ne 0 ]; then
- echo "Script failed with exit code: $exit_code"
- fi
- echo "Performing cleanup..."
- restore_network_access
- exit $exit_code
-}
-
-# Set up trap to ensure cleanup happens on exit
-trap cleanup_on_exit EXIT INT TERM
# Check if all required arguments are provided
if [ -z "$projectEndpoint" ] || [ -z "$solutionName" ] || [ -z "$gptModelName" ] || [ -z "$aiFoundryResourceId" ] || [ -z "$apiAppName" ] || [ -z "$resourceGroup" ] || [ -z "$searchEndpoint" ]; then
@@ -164,32 +57,68 @@ else
az login
fi
-echo "Getting signed in user id"
-signed_user_id=$(az ad signed-in-user show --query id -o tsv)
+echo "Getting principal id (user or service principal)"
+# Temporarily disable exit on error for principal detection
+set +e
+# Try to get signed-in user first (for interactive logins)
+signed_user_id=$(az ad signed-in-user show --query id -o tsv 2>/dev/null)
+
+# If that fails, we're likely using a service principal - get its object ID
+if [ -z "$signed_user_id" ]; then
+ echo "Not logged in as user, checking for service principal..."
+ account_info=$(az account show --query '{name:user.name, type:user.type}' -o json)
+ account_type=$(echo "$account_info" | jq -r '.type')
+
+ if [ "$account_type" = "servicePrincipal" ]; then
+ sp_name=$(echo "$account_info" | jq -r '.name')
+ echo "Logged in as service principal: $sp_name"
+ signed_user_id=$(az ad sp show --id "$sp_name" --query id -o tsv 2>/dev/null)
+
+ if [ -z "$signed_user_id" ]; then
+ echo "Warning: Could not get service principal object ID. Attempting to continue without role assignment..."
+ echo "Note: Ensure the service principal has necessary permissions assigned at subscription/resource group level."
+ SKIP_ROLE_ASSIGNMENT=true
+ else
+ echo "Service principal object ID: $signed_user_id"
+ fi
+ else
+ echo "Warning: Could not determine principal ID. Attempting to continue without role assignment..."
+ SKIP_ROLE_ASSIGNMENT=true
+ fi
+else
+ echo "Logged in as user: $signed_user_id"
+fi
+# Re-enable exit on error
+set -e
-echo "Checking if the user has Azure AI User role on the AI Foundry"
-role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list \
- --role "53ca6127-db72-4b80-b1b0-d745d6d5456d" \
- --scope "$aiFoundryResourceId" \
- --assignee "$signed_user_id" \
- --query "[].roleDefinitionId" -o tsv)
+echo "Checking if the principal has Azure AI User role on the AI Foundry"
-if [ -z "$role_assignment" ]; then
- echo "User does not have the Azure AI User role. Assigning the role..."
- MSYS_NO_PATHCONV=1 az role assignment create \
- --assignee "$signed_user_id" \
+if [ "$SKIP_ROLE_ASSIGNMENT" != "true" ] && [ -n "$signed_user_id" ]; then
+ role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list \
--role "53ca6127-db72-4b80-b1b0-d745d6d5456d" \
--scope "$aiFoundryResourceId" \
- --output none
-
- if [ $? -eq 0 ]; then
- echo "Azure AI User role assigned successfully."
+ --assignee "$signed_user_id" \
+ --query "[].roleDefinitionId" -o tsv)
+
+ if [ -z "$role_assignment" ]; then
+ echo "Principal does not have the Azure AI User role. Assigning the role..."
+ MSYS_NO_PATHCONV=1 az role assignment create \
+ --assignee "$signed_user_id" \
+ --role "53ca6127-db72-4b80-b1b0-d745d6d5456d" \
+ --scope "$aiFoundryResourceId" \
+ --output none
+
+ if [ $? -eq 0 ]; then
+ echo "Azure AI User role assigned successfully."
+ else
+ echo "Failed to assign Azure AI User role."
+ exit 1
+ fi
else
- echo "Failed to assign Azure AI User role."
- exit 1
+ echo "Principal already has the Azure AI User role."
fi
else
- echo "User already has the Azure AI User role."
+ echo "Skipping role assignment (will rely on existing permissions)"
fi
@@ -199,13 +128,6 @@ requirementFile="infra/scripts/agent_scripts/requirements.txt"
python -m pip install --upgrade pip
python -m pip install --quiet -r "$requirementFile"
-# Enable public network access for required services
-enable_public_access
-if [ $? -ne 0 ]; then
- echo "Error: Failed to enable public network access for services."
- exit 1
-fi
-
# Execute the Python scripts
echo "Running Python agents creation script..."
python_output=$(python infra/scripts/agent_scripts/01_create_agents.py --ai_project_endpoint="$projectEndpoint" --solution_name="$solutionName" --gpt_model_name="$gptModelName" --ai_search_endpoint="$searchEndpoint")
@@ -220,5 +142,4 @@ az webapp config appsettings set \
--settings FOUNDRY_CHAT_AGENT_ID="$chatAgentId" FOUNDRY_CUSTOM_PRODUCT_AGENT_ID="$productAgentId" FOUNDRY_POLICY_AGENT_ID="$policyAgentId" \
-o none
-echo "Environment variables updated for App Service: $apiAppName"
-echo "Network access will be restored to original settings..."
+echo "Environment variables updated for App Service: $apiAppName"
\ No newline at end of file
diff --git a/infra/scripts/data_scripts/run_upload_data_scripts.sh b/infra/scripts/data_scripts/run_upload_data_scripts.sh
index 7be42a13..3002f693 100644
--- a/infra/scripts/data_scripts/run_upload_data_scripts.sh
+++ b/infra/scripts/data_scripts/run_upload_data_scripts.sh
@@ -64,177 +64,6 @@ if [ -z "$cosmosdb_account" ]; then
cosmosdb_account=$(azd env get-value AZURE_COSMOSDB_ACCOUNT)
fi
-original_foundry_public_access=""
-original_cosmos_public_access=""
-original_cosmos_ip_filter=""
-
-# Function to enable public network access temporarily
-enable_public_access() {
- echo "=== Temporarily enabling public network access for services ==="
-
- # Enable public access for Cosmos DB
- echo "Configuring Cosmos DB network access: $cosmosdb_account"
-
- # Get Cosmos DB resource ID
- subscription_id=$(az account show --query id -o tsv)
- cosmos_resource_id="/subscriptions/${subscription_id}/resourceGroups/${resource_group}/providers/Microsoft.DocumentDB/databaseAccounts/${cosmosdb_account}"
-
- original_cosmos_public_access=$(MSYS_NO_PATHCONV=1 az resource show \
- --ids "$cosmos_resource_id" \
- --api-version 2021-04-15 \
- --query "properties.publicNetworkAccess" \
- --output tsv 2>/dev/null)
- echo "Original Cosmos DB public access: $original_cosmos_public_access"
-
- # Only modify Cosmos DB if it's not already enabled
- if [ "$original_cosmos_public_access" = "Enabled" ]; then
- echo "โ Cosmos DB public access already enabled - no changes needed"
- else
- echo "Getting current IP address..."
- current_ip=$(curl -s https://ipinfo.io/ip)
- echo "Current IP: $current_ip"
- # Get current firewall rules and public network access setting
- echo "Getting current firewall configuration..."
- original_cosmos_ip_filter=$(MSYS_NO_PATHCONV=1 az resource show \
- --ids "$cosmos_resource_id" \
- --api-version 2021-04-15 \
- --query "properties.ipRules" \
- --output json 2>/dev/null || echo "[]")
-
- echo "Cosmos DB public access is '$original_cosmos_public_access' - enabling access"
-
- # Add current IP to firewall rules and enable public network access
- echo "Adding current IP ($current_ip) to Cosmos DB firewall..."
- if MSYS_NO_PATHCONV=1 az resource update \
- --ids "$cosmos_resource_id" \
- --api-version 2021-04-15 \
- --set "properties.ipRules=[{\"ipAddressOrRange\":\"$current_ip\"}]" \
- --set "properties.publicNetworkAccess=Enabled" \
- --output none; then
- echo "โ Cosmos DB firewall updated to allow current IP"
- echo "โ Cosmos DB public network access enabled"
-
- # Wait longer for changes to propagate
- echo "Waiting for Cosmos DB network changes to take effect..."
- sleep 30
- echo "Network configuration should now be active"
- else
- echo "โ Warning: Failed to update Cosmos DB firewall. You may need to manually add IP $current_ip"
- echo " Please add this IP address in Azure Portal: Cosmos DB > $cosmosdb_account > Networking > Firewall"
- fi
- fi
-
- # Enable public access for AI Foundry
- # Extract the account resource ID (remove /projects/... part if present)
- aif_account_resource_id=$(echo "$aiFoundryResourceId" | sed 's|/projects/.*||')
- aif_resource_name=$(basename "$aif_account_resource_id")
- # Extract resource group from the AI Foundry account resource ID
- aif_resource_group=$(echo "$aif_account_resource_id" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
- # Extract subscription ID from the AI Foundry account resource ID
- aif_subscription_id=$(echo "$aif_account_resource_id" | sed -n 's|.*/subscriptions/\([^/]*\)/.*|\1|p')
-
- original_foundry_public_access=$(az cognitiveservices account show \
- --name "$aif_resource_name" \
- --resource-group "$aif_resource_group" \
- --subscription "$aif_subscription_id" \
- --query "properties.publicNetworkAccess" \
- --output tsv)
- if [ -z "$original_foundry_public_access" ] || [ "$original_foundry_public_access" = "null" ]; then
- echo "โ Info: Could not retrieve AI Foundry network access status."
- echo " AI Foundry network access might be managed differently."
- elif [ "$original_foundry_public_access" != "Enabled" ]; then
- echo "Current AI Foundry public access: $original_foundry_public_access"
- echo "Enabling public access for AI Foundry resource: $aif_resource_name (Resource Group: $aif_resource_group)"
- if MSYS_NO_PATHCONV=1 az resource update \
- --ids "$aif_account_resource_id" \
- --api-version 2024-10-01 \
- --set properties.publicNetworkAccess=Enabled properties.apiProperties="{}" \
- --output none; then
- echo "โ AI Foundry public access enabled"
- else
- echo "โ Warning: Failed to enable AI Foundry public access automatically."
- fi
- else
- echo "โ AI Foundry public access already enabled - no changes needed"
- fi
-
- # Wait a bit for changes to take effect
- echo "Waiting for network access changes to propagate..."
- sleep 10
- echo "=== Public network access enabled successfully ==="
- return 0
-}
-
-# Function to restore original network access settings
-restore_network_access() {
- echo "=== Restoring original network access settings ==="
-
- # Restore AI Foundry access only if it was changed from the original state
- if [ -n "$original_foundry_public_access" ] && [ "$original_foundry_public_access" != "Enabled" ]; then
- echo "Restoring AI Foundry public access to: $original_foundry_public_access"
- # Reconstruct the AI Foundry resource ID for restoration
- aif_account_resource_id=$(echo "$aiFoundryResourceId" | sed 's|/projects/.*||')
- # Try using the working approach to restore the original setting
- if MSYS_NO_PATHCONV=1 az resource update \
- --ids "$aif_account_resource_id" \
- --api-version 2024-10-01 \
- --set properties.publicNetworkAccess="$original_foundry_public_access" \
- --set properties.apiProperties.qnaAzureSearchEndpointKey="" \
- --set properties.networkAcls.bypass="AzureServices" \
- --output none 2>/dev/null; then
- echo "โ AI Foundry access restored"
- else
- echo "โ Warning: Failed to restore AI Foundry access automatically."
- echo " Please manually restore network access in the Azure portal if needed."
- fi
- else
- echo "AI Foundry access unchanged (no restoration needed)"
- fi
-
- # Restore Cosmos DB settings only if it was changed from the original state
- if [ -n "$original_cosmos_public_access" ] && [ "$original_cosmos_public_access" != "Enabled" ]; then
- echo "Restoring Cosmos DB settings..."
- subscription_id=$(az account show --query id -o tsv)
- cosmos_resource_id="/subscriptions/${subscription_id}/resourceGroups/${resource_group}/providers/Microsoft.DocumentDB/databaseAccounts/${cosmosdb_account}"
-
- # Prepare restore command with both firewall rules and public access setting
- restore_command="--set properties.ipRules=$original_cosmos_ip_filter"
- if [ -n "$original_cosmos_public_access" ] && [ "$original_cosmos_public_access" != "null" ]; then
- restore_command="$restore_command --set properties.publicNetworkAccess=$original_cosmos_public_access"
- echo "Restoring Cosmos DB public access to: $original_cosmos_public_access"
- fi
-
- if MSYS_NO_PATHCONV=1 az resource update \
- --ids "$cosmos_resource_id" \
- --api-version 2021-04-15 \
- $restore_command \
- --output none; then
- echo "โ Cosmos DB settings restored"
- else
- echo "โ Warning: Failed to restore Cosmos DB settings automatically."
- echo " Please manually check firewall and network settings in the Azure portal."
- fi
- else
- echo "Cosmos DB unchanged (no restoration needed)"
- fi
-
- echo "=== Network access restoration completed ==="
-}
-
-# Function to handle script cleanup on exit
-cleanup_on_exit() {
- exit_code=$?
- echo ""
- if [ $exit_code -ne 0 ]; then
- echo "Script failed with exit code: $exit_code"
- fi
- echo "Performing cleanup..."
- restore_network_access
- exit $exit_code
-}
-
-# Set up trap to ensure cleanup happens on exit
-trap cleanup_on_exit EXIT INT TERM
# Check if user is logged in to Azure
echo "Checking Azure authentication..."
@@ -246,110 +75,147 @@ else
az login
fi
-echo "Getting signed in user id"
-signed_user_id=$(az ad signed-in-user show --query id -o tsv)
+echo "Getting principal id (user or service principal)"
+# Try to get signed-in user first (for interactive logins)
+signed_user_id=$(az ad signed-in-user show --query id -o tsv 2>/dev/null)
+
+# If that fails, we're likely using a service principal - get its object ID
+if [ -z "$signed_user_id" ]; then
+ echo "Not logged in as user, checking for service principal..."
+ account_info=$(az account show --query '{name:user.name, type:user.type}' -o json)
+ account_type=$(echo "$account_info" | jq -r '.type')
+
+ if [ "$account_type" = "servicePrincipal" ]; then
+ sp_name=$(echo "$account_info" | jq -r '.name')
+ echo "Logged in as service principal: $sp_name"
+ signed_user_id=$(az ad sp show --id "$sp_name" --query id -o tsv 2>/dev/null)
+
+ if [ -z "$signed_user_id" ]; then
+ echo "Warning: Could not get service principal object ID. Attempting to continue without role assignment..."
+ echo "Note: Ensure the service principal has necessary permissions assigned at subscription/resource group level."
+ SKIP_ROLE_ASSIGNMENT=true
+ else
+ echo "Service principal object ID: $signed_user_id"
+ fi
+ else
+ echo "Warning: Could not determine principal ID. Attempting to continue without role assignment..."
+ SKIP_ROLE_ASSIGNMENT=true
+ fi
+else
+ echo "Logged in as user: $signed_user_id"
+fi
-echo "Checking if the user has Search roles on the AI Search Service"
+echo "Checking if the principal has Search roles on the AI Search Service"
# search service contributor role id: 7ca78c08-252a-4471-8644-bb5ff32d4ba0
# search index data contributor role id: 8ebe5a00-799e-43f5-93ac-243d3dce84a7
# search index data reader role id: 1407120a-92aa-4202-b7e9-c0e197c71c8f
-role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list \
- --role "7ca78c08-252a-4471-8644-bb5ff32d4ba0" \
- --scope "$aiSearchResourceId" \
- --assignee "$signed_user_id" \
- --query "[].roleDefinitionId" -o tsv)
-
-if [ -z "$role_assignment" ]; then
- echo "User does not have the search service contributor role. Assigning the role..."
- MSYS_NO_PATHCONV=1 az role assignment create \
- --assignee "$signed_user_id" \
+if [ "$SKIP_ROLE_ASSIGNMENT" != "true" ] && [ -n "$signed_user_id" ]; then
+ role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list \
--role "7ca78c08-252a-4471-8644-bb5ff32d4ba0" \
--scope "$aiSearchResourceId" \
- --output none
-
- if [ $? -eq 0 ]; then
- echo "Search service contributor role assigned successfully."
+ --assignee "$signed_user_id" \
+ --query "[].roleDefinitionId" -o tsv)
+
+ if [ -z "$role_assignment" ]; then
+ echo "Principal does not have the search service contributor role. Assigning the role..."
+ MSYS_NO_PATHCONV=1 az role assignment create \
+ --assignee "$signed_user_id" \
+ --role "7ca78c08-252a-4471-8644-bb5ff32d4ba0" \
+ --scope "$aiSearchResourceId" \
+ --output none
+
+ if [ $? -eq 0 ]; then
+ echo "Search service contributor role assigned successfully."
+ else
+ echo "Failed to assign search service contributor role."
+ exit 1
+ fi
else
- echo "Failed to assign search service contributor role."
- exit 1
+ echo "Principal already has the search service contributor role."
fi
-else
- echo "User already has the search service contributor role."
-fi
-role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list \
- --role "8ebe5a00-799e-43f5-93ac-243d3dce84a7" \
- --scope "$aiSearchResourceId" \
- --assignee "$signed_user_id" \
- --query "[].roleDefinitionId" -o tsv)
-
-if [ -z "$role_assignment" ]; then
- echo "User does not have the search index data contributor role. Assigning the role..."
- MSYS_NO_PATHCONV=1 az role assignment create \
- --assignee "$signed_user_id" \
+ role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list \
--role "8ebe5a00-799e-43f5-93ac-243d3dce84a7" \
--scope "$aiSearchResourceId" \
- --output none
-
- if [ $? -eq 0 ]; then
- echo "Search index data contributor role assigned successfully."
+ --assignee "$signed_user_id" \
+ --query "[].roleDefinitionId" -o tsv)
+
+ if [ -z "$role_assignment" ]; then
+ echo "Principal does not have the search index data contributor role. Assigning the role..."
+ MSYS_NO_PATHCONV=1 az role assignment create \
+ --assignee "$signed_user_id" \
+ --role "8ebe5a00-799e-43f5-93ac-243d3dce84a7" \
+ --scope "$aiSearchResourceId" \
+ --output none
+
+ if [ $? -eq 0 ]; then
+ echo "Search index data contributor role assigned successfully."
+ else
+ echo "Failed to assign search index data contributor role."
+ exit 1
+ fi
else
- echo "Failed to assign search index data contributor role."
- exit 1
+ echo "Principal already has the search index data contributor role."
fi
else
- echo "User already has the search index data contributor role."
+ echo "Skipping role assignment (will rely on existing permissions)"
fi
-role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list \
- --role "1407120a-92aa-4202-b7e9-c0e197c71c8f" \
- --scope "$aiSearchResourceId" \
- --assignee "$signed_user_id" \
- --query "[].roleDefinitionId" -o tsv)
-
-if [ -z "$role_assignment" ]; then
- echo "User does not have the search index data reader role. Assigning the role..."
- MSYS_NO_PATHCONV=1 az role assignment create \
- --assignee "$signed_user_id" \
+if [ "$SKIP_ROLE_ASSIGNMENT" != "true" ] && [ -n "$signed_user_id" ]; then
+ role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list \
--role "1407120a-92aa-4202-b7e9-c0e197c71c8f" \
--scope "$aiSearchResourceId" \
- --output none
-
- if [ $? -eq 0 ]; then
- echo "Search index data reader role assigned successfully."
+ --assignee "$signed_user_id" \
+ --query "[].roleDefinitionId" -o tsv)
+
+ if [ -z "$role_assignment" ]; then
+ echo "Principal does not have the search index data reader role. Assigning the role..."
+ MSYS_NO_PATHCONV=1 az role assignment create \
+ --assignee "$signed_user_id" \
+ --role "1407120a-92aa-4202-b7e9-c0e197c71c8f" \
+ --scope "$aiSearchResourceId" \
+ --output none
+
+ if [ $? -eq 0 ]; then
+ echo "Search index data reader role assigned successfully."
+ else
+ echo "Failed to assign search index data reader role."
+ exit 1
+ fi
else
- echo "Failed to assign search index data reader role."
- exit 1
+ echo "Principal already has the search index data reader role."
fi
-else
- echo "User already has the search index data reader role."
fi
-# Check if the user has the Cosmos DB Built-in Data Contributor role
-echo "Checking if user has the Cosmos DB Built-in Data Contributor role"
-roleExists=$(az cosmosdb sql role assignment list \
- --resource-group $resource_group \
- --account-name $cosmosdb_account \
- --query "[?roleDefinitionId.ends_with(@, '00000000-0000-0000-0000-000000000002') && principalId == '$signed_user_id']" -o tsv)
-
-# Check if the role exists
-if [ -n "$roleExists" ]; then
- echo "User already has the Cosmos DB Built-in Data Contributer role."
-else
- echo "User does not have the Cosmos DB Built-in Data Contributer role. Assigning the role."
- MSYS_NO_PATHCONV=1 az cosmosdb sql role assignment create \
+# Check if the principal has the Cosmos DB Built-in Data Contributor role
+if [ "$SKIP_ROLE_ASSIGNMENT" != "true" ] && [ -n "$signed_user_id" ]; then
+ echo "Checking if principal has the Cosmos DB Built-in Data Contributor role"
+ roleExists=$(az cosmosdb sql role assignment list \
--resource-group $resource_group \
--account-name $cosmosdb_account \
- --role-definition-id 00000000-0000-0000-0000-000000000002 \
- --principal-id $signed_user_id \
- --scope "/" \
- --output none
- if [ $? -eq 0 ]; then
- echo "Cosmos DB Built-in Data Contributer role assigned successfully."
+ --query "[?roleDefinitionId.ends_with(@, '00000000-0000-0000-0000-000000000002') && principalId == '$signed_user_id']" -o tsv)
+
+ # Check if the role exists
+ if [ -n "$roleExists" ]; then
+ echo "Principal already has the Cosmos DB Built-in Data Contributor role."
else
- echo "Failed to assign Cosmos DB Built-in Data Contributer role."
+ echo "Principal does not have the Cosmos DB Built-in Data Contributor role. Assigning the role."
+ MSYS_NO_PATHCONV=1 az cosmosdb sql role assignment create \
+ --resource-group $resource_group \
+ --account-name $cosmosdb_account \
+ --role-definition-id 00000000-0000-0000-0000-000000000002 \
+ --principal-id $signed_user_id \
+ --scope "/" \
+ --output none
+ if [ $? -eq 0 ]; then
+ echo "Cosmos DB Built-in Data Contributor role assigned successfully."
+ else
+ echo "Failed to assign Cosmos DB Built-in Data Contributor role."
+ fi
fi
+else
+ echo "Skipping Cosmos DB role assignment (will rely on existing permissions)"
fi
# role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list \
@@ -413,16 +279,8 @@ requirementFile="infra/scripts/data_scripts/requirements.txt"
python -m pip install --upgrade pip
python -m pip install --quiet -r "$requirementFile"
-# Enable public network access for required services
-enable_public_access
-if [ $? -ne 0 ]; then
- echo "Error: Failed to enable public network access for services."
- exit 1
-fi
# python pip install -r infra/scripts/data_scripts/requirements.txt --quiet
python infra/scripts/data_scripts/01_create_products_search_index.py --ai_search_endpoint="$ai_search_endpoint" --azure_openai_endpoint="$azure_openai_endpoint" --embedding_model_name="$embedding_model_name"
python infra/scripts/data_scripts/02_create_policies_search_index.py --ai_search_endpoint="$ai_search_endpoint" --azure_openai_endpoint="$azure_openai_endpoint" --embedding_model_name="$embedding_model_name"
-python infra/scripts/data_scripts/03_write_products_to_cosmos.py --cosmosdb_account="$cosmosdb_account"
-
-echo "Network access will be restored to original settings..."
\ No newline at end of file
+python infra/scripts/data_scripts/03_write_products_to_cosmos.py --cosmosdb_account="$cosmosdb_account"
\ No newline at end of file
From 204d2774c2b0cca08ec6a05f5034b3ca381c8594 Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Tue, 27 Jan 2026 13:04:51 +0530
Subject: [PATCH 04/38] Trim whitespace from environment variables in
checkquota.sh
---
infra/scripts/checkquota.sh | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/infra/scripts/checkquota.sh b/infra/scripts/checkquota.sh
index 4a759dbe..8a1876d7 100644
--- a/infra/scripts/checkquota.sh
+++ b/infra/scripts/checkquota.sh
@@ -1,14 +1,25 @@
#!/bin/bash
+# Function to trim leading and trailing whitespace
+trim() {
+ local var="$*"
+ # Remove leading whitespace
+ var="${var#"${var%%[![:space:]]*}"}"
+ # Remove trailing whitespace
+ var="${var%"${var##*[![:space:]]}"}"
+ printf '%s' "$var"
+}
+
# List of Azure regions to check for quota (update as needed)
IFS=', ' read -ra REGIONS <<< "$AZURE_REGIONS"
-SUBSCRIPTION_ID="${AZURE_SUBSCRIPTION_ID}"
+# Trim whitespace from environment variables to avoid issues with leading/trailing spaces
+SUBSCRIPTION_ID=$(trim "${AZURE_SUBSCRIPTION_ID}")
GPT_MIN_CAPACITY="${GPT_MIN_CAPACITY:-10}"
EMBEDDING_MIN_CAPACITY="${EMBEDDING_MIN_CAPACITY:-10}"
-AZURE_CLIENT_ID="${AZURE_CLIENT_ID}"
-AZURE_TENANT_ID="${AZURE_TENANT_ID}"
-AZURE_CLIENT_SECRET="${AZURE_CLIENT_SECRET}"
+AZURE_CLIENT_ID=$(trim "${AZURE_CLIENT_ID}")
+AZURE_TENANT_ID=$(trim "${AZURE_TENANT_ID}")
+AZURE_CLIENT_SECRET=$(trim "${AZURE_CLIENT_SECRET}")
# Authenticate using Managed Identity
echo "Authentication using Managed Identity..."
From d827883e107cee3c9382a02d166a60965eceff79 Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Tue, 27 Jan 2026 13:52:11 +0530
Subject: [PATCH 05/38] Update test automation workflow name and description
for clarity
---
.github/workflows/test-automation-v2.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/test-automation-v2.yml b/.github/workflows/test-automation-v2.yml
index 625f6e2f..cdacf56e 100644
--- a/.github/workflows/test-automation-v2.yml
+++ b/.github/workflows/test-automation-v2.yml
@@ -1,4 +1,4 @@
-name: Test Automation Code Modernization - v2
+name: Test Automation BYOCC - v2
permissions:
contents: read
@@ -9,7 +9,7 @@ on:
TEST_URL:
required: true
type: string
- description: "Web URL for code modernization"
+ description: "Web URL for BYOCC"
TEST_SUITE:
required: false
type: string
From bf49fd6b57278deb6ddad4251716aa628ff984cf Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Tue, 27 Jan 2026 13:56:14 +0530
Subject: [PATCH 06/38] Add WAF support for enabling and restoring public
network access for Cosmos DB and AI Services
---
.github/workflows/job-deploy-linux.yml | 58 ++++++++++++++++++++++++
.github/workflows/job-deploy-windows.yml | 58 ++++++++++++++++++++++++
2 files changed, 116 insertions(+)
diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml
index 63212ddb..225f05a2 100644
--- a/.github/workflows/job-deploy-linux.yml
+++ b/.github/workflows/job-deploy-linux.yml
@@ -286,6 +286,38 @@ jobs:
# Upgrade pip
python3 -m pip install --upgrade pip
+
+ - name: Enable Public Network Access for Data Upload (WAF Only)
+ if: inputs.WAF_ENABLED == 'true'
+ shell: bash
+ env:
+ INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ run: |
+ echo "๐ Temporarily enabling public network access for data upload..."
+
+ # Get Cosmos DB account name
+ COSMOS_ACCOUNT=$(az cosmosdb list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
+ if [[ -n "$COSMOS_ACCOUNT" ]]; then
+ echo "Enabling public access for Cosmos DB: $COSMOS_ACCOUNT"
+ az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Enabled
+ echo "โ
Cosmos DB public access enabled"
+ else
+ echo "โ ๏ธ No Cosmos DB account found in resource group"
+ fi
+
+ # Get AI Services account name
+ AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
+ if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
+ echo "Enabling public access for AI Services: $AI_SERVICES_ACCOUNT"
+ az cognitiveservices account update --name "$AI_SERVICES_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Enabled
+ echo "โ
AI Services public access enabled"
+ else
+ echo "โ ๏ธ No AI Services account found in resource group"
+ fi
+
+ # Wait for network changes to propagate
+ echo "โณ Waiting 30 seconds for network changes to propagate..."
+ sleep 30
- name: Run Data Upload Scripts
shell: bash
@@ -322,6 +354,32 @@ jobs:
exit 1
fi
+ - name: Restore Private Network Access (WAF Only)
+ if: always() && inputs.WAF_ENABLED == 'true'
+ shell: bash
+ env:
+ INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ run: |
+ echo "๐ Restoring private network access settings..."
+
+ # Get Cosmos DB account name
+ COSMOS_ACCOUNT=$(az cosmosdb list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
+ if [[ -n "$COSMOS_ACCOUNT" ]]; then
+ echo "Disabling public access for Cosmos DB: $COSMOS_ACCOUNT"
+ az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Disabled || true
+ echo "โ
Cosmos DB public access disabled"
+ fi
+
+ # Get AI Services account name
+ AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
+ if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
+ echo "Disabling public access for AI Services: $AI_SERVICES_ACCOUNT"
+ az cognitiveservices account update --name "$AI_SERVICES_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Disabled || true
+ echo "โ
AI Services public access disabled"
+ fi
+
+ echo "๐ Private network access restored"
+
- name: Generate Deployment Summary
if: always()
shell: bash
diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml
index 77f05fa9..89c5418c 100644
--- a/.github/workflows/job-deploy-windows.yml
+++ b/.github/workflows/job-deploy-windows.yml
@@ -285,6 +285,38 @@ jobs:
"WEBAPP_URL=$WEBAPP_URL" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
"WEBAPP_URL=$WEBAPP_URL" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
+ - name: Enable Public Network Access for Data Upload (WAF Only)
+ if: inputs.WAF_ENABLED == 'true'
+ shell: bash
+ env:
+ INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ run: |
+ echo "๐ Temporarily enabling public network access for data upload..."
+
+ # Get Cosmos DB account name
+ COSMOS_ACCOUNT=$(az cosmosdb list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
+ if [[ -n "$COSMOS_ACCOUNT" ]]; then
+ echo "Enabling public access for Cosmos DB: $COSMOS_ACCOUNT"
+ az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Enabled
+ echo "โ
Cosmos DB public access enabled"
+ else
+ echo "โ ๏ธ No Cosmos DB account found in resource group"
+ fi
+
+ # Get AI Services account name
+ AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
+ if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
+ echo "Enabling public access for AI Services: $AI_SERVICES_ACCOUNT"
+ az cognitiveservices account update --name "$AI_SERVICES_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Enabled
+ echo "โ
AI Services public access enabled"
+ else
+ echo "โ ๏ธ No AI Services account found in resource group"
+ fi
+
+ # Wait for network changes to propagate
+ echo "โณ Waiting 30 seconds for network changes to propagate..."
+ sleep 30
+
- name: Run Post-Deployment Data Upload Scripts
shell: pwsh
run: |
@@ -320,6 +352,32 @@ jobs:
bash ./$env:INFRA_PATH/scripts/agent_scripts/run_create_agents_scripts.sh
+ - name: Restore Private Network Access (WAF Only)
+ if: always() && inputs.WAF_ENABLED == 'true'
+ shell: bash
+ env:
+ INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ run: |
+ echo "๐ Restoring private network access settings..."
+
+ # Get Cosmos DB account name
+ COSMOS_ACCOUNT=$(az cosmosdb list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
+ if [[ -n "$COSMOS_ACCOUNT" ]]; then
+ echo "Disabling public access for Cosmos DB: $COSMOS_ACCOUNT"
+ az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Disabled || true
+ echo "โ
Cosmos DB public access disabled"
+ fi
+
+ # Get AI Services account name
+ AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
+ if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
+ echo "Disabling public access for AI Services: $AI_SERVICES_ACCOUNT"
+ az cognitiveservices account update --name "$AI_SERVICES_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Disabled || true
+ echo "โ
AI Services public access disabled"
+ fi
+
+ echo "๐ Private network access restored"
+
- name: Generate Deployment Summary
if: always()
shell: bash
From b0ea1d5d3db23a257a7d612f369b92a08f29f206 Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Tue, 27 Jan 2026 14:20:47 +0530
Subject: [PATCH 07/38] Refactor validation regex for
AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID in deployment workflows
---
.github/workflows/deploy-linux.yml | 2 +-
.github/workflows/deploy-windows.yml | 2 +-
.github/workflows/job-deploy-linux.yml | 2 +-
.github/workflows/job-deploy-windows.yml | 2 +-
.github/workflows/job-deploy.yml | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/deploy-linux.yml b/.github/workflows/deploy-linux.yml
index 683f2f2c..3674f1c7 100644
--- a/.github/workflows/deploy-linux.yml
+++ b/.github/workflows/deploy-linux.yml
@@ -186,7 +186,7 @@ jobs:
# Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (optional, Azure Resource ID format)
if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then
- if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/microsoft\.operationalinsights/workspaces/[^/]+$ ]]; then
+ if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/[rR]esource[gG]roups/[^/]+/providers/[mM]icrosoft\.[oO]perational[iI]nsights/workspaces/[^/]+$ ]]; then
echo "โ ERROR: AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID is invalid. Must be a valid Azure Resource ID format:"
echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}"
echo " Got: '$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID'"
diff --git a/.github/workflows/deploy-windows.yml b/.github/workflows/deploy-windows.yml
index 6e35c534..2987af50 100644
--- a/.github/workflows/deploy-windows.yml
+++ b/.github/workflows/deploy-windows.yml
@@ -182,7 +182,7 @@ jobs:
# Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (optional, Azure Resource ID format)
if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then
- if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/microsoft\.operationalinsights/workspaces/[^/]+$ ]]; then
+ if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/[rR]esource[gG]roups/[^/]+/providers/[mM]icrosoft\.[oO]perational[iI]nsights/workspaces/[^/]+$ ]]; then
echo "โ ERROR: AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID is invalid. Must be a valid Azure Resource ID format:"
echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}"
echo " Got: '$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID'"
diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml
index 225f05a2..c156f6b4 100644
--- a/.github/workflows/job-deploy-linux.yml
+++ b/.github/workflows/job-deploy-linux.yml
@@ -154,7 +154,7 @@ jobs:
# Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (optional, if provided must be valid Resource ID)
if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then
- if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/microsoft\.operationalinsights/workspaces/[^/]+$ ]]; then
+ if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/[rR]esource[gG]roups/[^/]+/providers/[mM]icrosoft\.[oO]perational[iI]nsights/workspaces/[^/]+$ ]]; then
echo "โ ERROR: AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID is invalid. Must be a valid Azure Resource ID format:"
echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}"
echo " Got: '$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID'"
diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml
index 89c5418c..4c6560d5 100644
--- a/.github/workflows/job-deploy-windows.yml
+++ b/.github/workflows/job-deploy-windows.yml
@@ -155,7 +155,7 @@ jobs:
# Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (optional, if provided must be valid Resource ID)
if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then
- if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/microsoft\.operationalinsights/workspaces/[^/]+$ ]]; then
+ if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/[rR]esource[gG]roups/[^/]+/providers/[mM]icrosoft\.[oO]perational[iI]nsights/workspaces/[^/]+$ ]]; then
echo "โ ERROR: AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID is invalid. Must be a valid Azure Resource ID format:"
echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}"
echo " Got: '$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID'"
diff --git a/.github/workflows/job-deploy.yml b/.github/workflows/job-deploy.yml
index 7c44f64e..bcabd32c 100644
--- a/.github/workflows/job-deploy.yml
+++ b/.github/workflows/job-deploy.yml
@@ -191,7 +191,7 @@ jobs:
# Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (Azure Resource ID format)
if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then
- if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/microsoft\.operationalinsights/workspaces/[^/]+$ ]]; then
+ if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/[rR]esource[gG]roups/[^/]+/providers/[mM]icrosoft\.[oO]perational[iI]nsights/workspaces/[^/]+$ ]]; then
echo "โ ERROR: AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID is invalid. Must be a valid Azure Resource ID format:"
echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}"
echo " Got: '$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID'"
From 471198c96dad0e8f5b8df8a4281dc5fc6712731e Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Tue, 27 Jan 2026 15:02:32 +0530
Subject: [PATCH 08/38] Update AI Services public access management to use
Azure REST API for enabling and disabling public network access
---
.github/workflows/job-deploy-linux.yml | 10 ++++++++--
.github/workflows/job-deploy-windows.yml | 10 ++++++++--
2 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml
index c156f6b4..4614f1b0 100644
--- a/.github/workflows/job-deploy-linux.yml
+++ b/.github/workflows/job-deploy-linux.yml
@@ -309,7 +309,10 @@ jobs:
AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
echo "Enabling public access for AI Services: $AI_SERVICES_ACCOUNT"
- az cognitiveservices account update --name "$AI_SERVICES_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Enabled
+ SUBSCRIPTION_ID=$(az account show --query id -o tsv)
+ az rest --method PATCH \
+ --url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${INPUT_RESOURCE_GROUP_NAME}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
+ --body '{"properties":{"publicNetworkAccess":"Enabled"}}'
echo "โ
AI Services public access enabled"
else
echo "โ ๏ธ No AI Services account found in resource group"
@@ -374,7 +377,10 @@ jobs:
AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
echo "Disabling public access for AI Services: $AI_SERVICES_ACCOUNT"
- az cognitiveservices account update --name "$AI_SERVICES_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Disabled || true
+ SUBSCRIPTION_ID=$(az account show --query id -o tsv 2>/dev/null)
+ az rest --method PATCH \
+ --url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${INPUT_RESOURCE_GROUP_NAME}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
+ --body '{"properties":{"publicNetworkAccess":"Disabled"}}' || true
echo "โ
AI Services public access disabled"
fi
diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml
index 4c6560d5..49fc98a7 100644
--- a/.github/workflows/job-deploy-windows.yml
+++ b/.github/workflows/job-deploy-windows.yml
@@ -307,7 +307,10 @@ jobs:
AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
echo "Enabling public access for AI Services: $AI_SERVICES_ACCOUNT"
- az cognitiveservices account update --name "$AI_SERVICES_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Enabled
+ SUBSCRIPTION_ID=$(az account show --query id -o tsv)
+ az rest --method PATCH \
+ --url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${INPUT_RESOURCE_GROUP_NAME}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
+ --body '{"properties":{"publicNetworkAccess":"Enabled"}}'
echo "โ
AI Services public access enabled"
else
echo "โ ๏ธ No AI Services account found in resource group"
@@ -372,7 +375,10 @@ jobs:
AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
echo "Disabling public access for AI Services: $AI_SERVICES_ACCOUNT"
- az cognitiveservices account update --name "$AI_SERVICES_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Disabled || true
+ SUBSCRIPTION_ID=$(az account show --query id -o tsv 2>/dev/null)
+ az rest --method PATCH \
+ --url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${INPUT_RESOURCE_GROUP_NAME}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
+ --body '{"properties":{"publicNetworkAccess":"Disabled"}}' || true
echo "โ
AI Services public access disabled"
fi
From 7f3aebf65f99c6910e6bd17e212d0e2654fe08cb Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Tue, 27 Jan 2026 15:14:09 +0530
Subject: [PATCH 09/38] Enhance public access management for AI Services and AI
Search in deployment workflows
---
.github/workflows/job-deploy-linux.yml | 34 +++++++++++++++++++++---
.github/workflows/job-deploy-windows.yml | 34 +++++++++++++++++++++---
2 files changed, 60 insertions(+), 8 deletions(-)
diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml
index 4614f1b0..c0d1114a 100644
--- a/.github/workflows/job-deploy-linux.yml
+++ b/.github/workflows/job-deploy-linux.yml
@@ -294,6 +294,8 @@ jobs:
INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
run: |
echo "๐ Temporarily enabling public network access for data upload..."
+ SUBSCRIPTION_ID=$(az account show --query id -o tsv)
+ SP_OBJECT_ID=$(az ad sp show --id ${{ secrets.AZURE_CLIENT_ID }} --query id -o tsv)
# Get Cosmos DB account name
COSMOS_ACCOUNT=$(az cosmosdb list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
@@ -305,22 +307,38 @@ jobs:
echo "โ ๏ธ No Cosmos DB account found in resource group"
fi
- # Get AI Services account name
+ # Get AI Services account name and enable public access + assign role
AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
echo "Enabling public access for AI Services: $AI_SERVICES_ACCOUNT"
- SUBSCRIPTION_ID=$(az account show --query id -o tsv)
az rest --method PATCH \
--url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${INPUT_RESOURCE_GROUP_NAME}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
--body '{"properties":{"publicNetworkAccess":"Enabled"}}'
echo "โ
AI Services public access enabled"
+
+ # Assign Cognitive Services OpenAI User role to service principal
+ echo "Assigning Cognitive Services OpenAI User role to service principal..."
+ AI_SERVICES_ID=$(az cognitiveservices account show --name "$AI_SERVICES_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query id -o tsv)
+ az role assignment create --assignee-object-id "$SP_OBJECT_ID" --assignee-principal-type ServicePrincipal \
+ --role "Cognitive Services OpenAI User" --scope "$AI_SERVICES_ID" 2>/dev/null || echo "Role may already be assigned"
+ echo "โ
Cognitive Services OpenAI User role assigned"
else
echo "โ ๏ธ No AI Services account found in resource group"
fi
+ # Get AI Search service and enable public access
+ SEARCH_SERVICE=$(az search service list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
+ if [[ -n "$SEARCH_SERVICE" ]]; then
+ echo "Enabling public access for AI Search: $SEARCH_SERVICE"
+ az search service update --name "$SEARCH_SERVICE" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-access enabled
+ echo "โ
AI Search public access enabled"
+ else
+ echo "โ ๏ธ No AI Search service found in resource group"
+ fi
+
# Wait for network changes to propagate
- echo "โณ Waiting 30 seconds for network changes to propagate..."
- sleep 30
+ echo "โณ Waiting 60 seconds for network changes to propagate..."
+ sleep 60
- name: Run Data Upload Scripts
shell: bash
@@ -384,6 +402,14 @@ jobs:
echo "โ
AI Services public access disabled"
fi
+ # Get AI Search service and disable public access
+ SEARCH_SERVICE=$(az search service list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
+ if [[ -n "$SEARCH_SERVICE" ]]; then
+ echo "Disabling public access for AI Search: $SEARCH_SERVICE"
+ az search service update --name "$SEARCH_SERVICE" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-access disabled || true
+ echo "โ
AI Search public access disabled"
+ fi
+
echo "๐ Private network access restored"
- name: Generate Deployment Summary
diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml
index 49fc98a7..9748849a 100644
--- a/.github/workflows/job-deploy-windows.yml
+++ b/.github/workflows/job-deploy-windows.yml
@@ -292,6 +292,8 @@ jobs:
INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
run: |
echo "๐ Temporarily enabling public network access for data upload..."
+ SUBSCRIPTION_ID=$(az account show --query id -o tsv)
+ SP_OBJECT_ID=$(az ad sp show --id ${{ secrets.AZURE_CLIENT_ID }} --query id -o tsv)
# Get Cosmos DB account name
COSMOS_ACCOUNT=$(az cosmosdb list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
@@ -303,22 +305,38 @@ jobs:
echo "โ ๏ธ No Cosmos DB account found in resource group"
fi
- # Get AI Services account name
+ # Get AI Services account name and enable public access + assign role
AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
echo "Enabling public access for AI Services: $AI_SERVICES_ACCOUNT"
- SUBSCRIPTION_ID=$(az account show --query id -o tsv)
az rest --method PATCH \
--url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${INPUT_RESOURCE_GROUP_NAME}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
--body '{"properties":{"publicNetworkAccess":"Enabled"}}'
echo "โ
AI Services public access enabled"
+
+ # Assign Cognitive Services OpenAI User role to service principal
+ echo "Assigning Cognitive Services OpenAI User role to service principal..."
+ AI_SERVICES_ID=$(az cognitiveservices account show --name "$AI_SERVICES_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query id -o tsv)
+ az role assignment create --assignee-object-id "$SP_OBJECT_ID" --assignee-principal-type ServicePrincipal \
+ --role "Cognitive Services OpenAI User" --scope "$AI_SERVICES_ID" 2>/dev/null || echo "Role may already be assigned"
+ echo "โ
Cognitive Services OpenAI User role assigned"
else
echo "โ ๏ธ No AI Services account found in resource group"
fi
+ # Get AI Search service and enable public access
+ SEARCH_SERVICE=$(az search service list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
+ if [[ -n "$SEARCH_SERVICE" ]]; then
+ echo "Enabling public access for AI Search: $SEARCH_SERVICE"
+ az search service update --name "$SEARCH_SERVICE" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-access enabled
+ echo "โ
AI Search public access enabled"
+ else
+ echo "โ ๏ธ No AI Search service found in resource group"
+ fi
+
# Wait for network changes to propagate
- echo "โณ Waiting 30 seconds for network changes to propagate..."
- sleep 30
+ echo "โณ Waiting 60 seconds for network changes to propagate..."
+ sleep 60
- name: Run Post-Deployment Data Upload Scripts
shell: pwsh
@@ -382,6 +400,14 @@ jobs:
echo "โ
AI Services public access disabled"
fi
+ # Get AI Search service and disable public access
+ SEARCH_SERVICE=$(az search service list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
+ if [[ -n "$SEARCH_SERVICE" ]]; then
+ echo "Disabling public access for AI Search: $SEARCH_SERVICE"
+ az search service update --name "$SEARCH_SERVICE" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-access disabled || true
+ echo "โ
AI Search public access disabled"
+ fi
+
echo "๐ Private network access restored"
- name: Generate Deployment Summary
From f08a49e1d612d04580caf4af14e28cf6783869d4 Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Tue, 27 Jan 2026 15:56:44 +0530
Subject: [PATCH 10/38] Add wait step for data propagation in deployment
workflows
---
.github/workflows/job-deploy-linux.yml | 7 +++++++
.github/workflows/job-deploy-windows.yml | 7 +++++++
2 files changed, 14 insertions(+)
diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml
index c0d1114a..de1ad266 100644
--- a/.github/workflows/job-deploy-linux.yml
+++ b/.github/workflows/job-deploy-linux.yml
@@ -375,6 +375,13 @@ jobs:
exit 1
fi
+ - name: Wait for Data Propagation
+ shell: bash
+ run: |
+ echo "โณ Waiting 5 minutes for data to propagate and indexes to update..."
+ sleep 300
+ echo "โ
Wait completed, data should now be available"
+
- name: Restore Private Network Access (WAF Only)
if: always() && inputs.WAF_ENABLED == 'true'
shell: bash
diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml
index 9748849a..8c6fe6ea 100644
--- a/.github/workflows/job-deploy-windows.yml
+++ b/.github/workflows/job-deploy-windows.yml
@@ -373,6 +373,13 @@ jobs:
bash ./$env:INFRA_PATH/scripts/agent_scripts/run_create_agents_scripts.sh
+ - name: Wait for Data Propagation
+ shell: bash
+ run: |
+ echo "โณ Waiting 5 minutes for data to propagate and indexes to update..."
+ sleep 300
+ echo "โ
Wait completed, data should now be available"
+
- name: Restore Private Network Access (WAF Only)
if: always() && inputs.WAF_ENABLED == 'true'
shell: bash
From 11fa07f1fac0075cfc31fd39fad6701a7bbdeb2e Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Tue, 27 Jan 2026 16:21:04 +0530
Subject: [PATCH 11/38] feat: Enhance public access management for Cosmos DB
and AI Services in deployment workflows
---
.github/workflows/job-deploy-linux.yml | 38 ++++++++++++++++++------
.github/workflows/job-deploy-windows.yml | 38 ++++++++++++++++++------
2 files changed, 58 insertions(+), 18 deletions(-)
diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml
index de1ad266..814c920d 100644
--- a/.github/workflows/job-deploy-linux.yml
+++ b/.github/workflows/job-deploy-linux.yml
@@ -292,12 +292,14 @@ jobs:
shell: bash
env:
INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ INPUT_EXP: ${{ inputs.EXP }}
+ INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
run: |
echo "๐ Temporarily enabling public network access for data upload..."
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
SP_OBJECT_ID=$(az ad sp show --id ${{ secrets.AZURE_CLIENT_ID }} --query id -o tsv)
- # Get Cosmos DB account name
+ # Get Cosmos DB account name and enable public access
COSMOS_ACCOUNT=$(az cosmosdb list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
if [[ -n "$COSMOS_ACCOUNT" ]]; then
echo "Enabling public access for Cosmos DB: $COSMOS_ACCOUNT"
@@ -307,23 +309,40 @@ jobs:
echo "โ ๏ธ No Cosmos DB account found in resource group"
fi
- # Get AI Services account name and enable public access + assign role
+ # Get AI Services account - check current RG first, then EXP RG if needed
AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
+ AI_SERVICES_RG="$INPUT_RESOURCE_GROUP_NAME"
+
+ # If EXP mode and no AI Services in current RG, get from existing AI project RG
+ if [[ -z "$AI_SERVICES_ACCOUNT" && "$INPUT_EXP" == "true" ]]; then
+ echo "EXP mode: Looking for AI Services in existing AI project resource group..."
+ if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
+ EXP_RG=$(echo "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ else
+ EXP_RG=$(echo "${{ vars.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ fi
+ if [[ -n "$EXP_RG" ]]; then
+ AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$EXP_RG" --query "[0].name" -o tsv)
+ AI_SERVICES_RG="$EXP_RG"
+ echo "Found AI Services in EXP resource group: $EXP_RG"
+ fi
+ fi
+
if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
- echo "Enabling public access for AI Services: $AI_SERVICES_ACCOUNT"
+ echo "Enabling public access for AI Services: $AI_SERVICES_ACCOUNT (RG: $AI_SERVICES_RG)"
az rest --method PATCH \
- --url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${INPUT_RESOURCE_GROUP_NAME}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
+ --url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${AI_SERVICES_RG}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
--body '{"properties":{"publicNetworkAccess":"Enabled"}}'
echo "โ
AI Services public access enabled"
# Assign Cognitive Services OpenAI User role to service principal
echo "Assigning Cognitive Services OpenAI User role to service principal..."
- AI_SERVICES_ID=$(az cognitiveservices account show --name "$AI_SERVICES_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query id -o tsv)
+ AI_SERVICES_ID=$(az cognitiveservices account show --name "$AI_SERVICES_ACCOUNT" --resource-group "$AI_SERVICES_RG" --query id -o tsv)
az role assignment create --assignee-object-id "$SP_OBJECT_ID" --assignee-principal-type ServicePrincipal \
--role "Cognitive Services OpenAI User" --scope "$AI_SERVICES_ID" 2>/dev/null || echo "Role may already be assigned"
echo "โ
Cognitive Services OpenAI User role assigned"
else
- echo "โ ๏ธ No AI Services account found in resource group"
+ echo "โ ๏ธ No AI Services account found"
fi
# Get AI Search service and enable public access
@@ -336,9 +355,10 @@ jobs:
echo "โ ๏ธ No AI Search service found in resource group"
fi
- # Wait for network changes to propagate
- echo "โณ Waiting 60 seconds for network changes to propagate..."
- sleep 60
+ # Wait for network changes to propagate (increased to 2 minutes for Cosmos DB)
+ echo "โณ Waiting 2 minutes for network changes to propagate..."
+ sleep 120
+ echo "โ
Network changes should now be propagated"
- name: Run Data Upload Scripts
shell: bash
diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml
index 8c6fe6ea..ef0a48e8 100644
--- a/.github/workflows/job-deploy-windows.yml
+++ b/.github/workflows/job-deploy-windows.yml
@@ -290,12 +290,14 @@ jobs:
shell: bash
env:
INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ INPUT_EXP: ${{ inputs.EXP }}
+ INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
run: |
echo "๐ Temporarily enabling public network access for data upload..."
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
SP_OBJECT_ID=$(az ad sp show --id ${{ secrets.AZURE_CLIENT_ID }} --query id -o tsv)
- # Get Cosmos DB account name
+ # Get Cosmos DB account name and enable public access
COSMOS_ACCOUNT=$(az cosmosdb list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
if [[ -n "$COSMOS_ACCOUNT" ]]; then
echo "Enabling public access for Cosmos DB: $COSMOS_ACCOUNT"
@@ -305,23 +307,40 @@ jobs:
echo "โ ๏ธ No Cosmos DB account found in resource group"
fi
- # Get AI Services account name and enable public access + assign role
+ # Get AI Services account - check current RG first, then EXP RG if needed
AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
+ AI_SERVICES_RG="$INPUT_RESOURCE_GROUP_NAME"
+
+ # If EXP mode and no AI Services in current RG, get from existing AI project RG
+ if [[ -z "$AI_SERVICES_ACCOUNT" && "$INPUT_EXP" == "true" ]]; then
+ echo "EXP mode: Looking for AI Services in existing AI project resource group..."
+ if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
+ EXP_RG=$(echo "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ else
+ EXP_RG=$(echo "${{ vars.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ fi
+ if [[ -n "$EXP_RG" ]]; then
+ AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$EXP_RG" --query "[0].name" -o tsv)
+ AI_SERVICES_RG="$EXP_RG"
+ echo "Found AI Services in EXP resource group: $EXP_RG"
+ fi
+ fi
+
if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
- echo "Enabling public access for AI Services: $AI_SERVICES_ACCOUNT"
+ echo "Enabling public access for AI Services: $AI_SERVICES_ACCOUNT (RG: $AI_SERVICES_RG)"
az rest --method PATCH \
- --url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${INPUT_RESOURCE_GROUP_NAME}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
+ --url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${AI_SERVICES_RG}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
--body '{"properties":{"publicNetworkAccess":"Enabled"}}'
echo "โ
AI Services public access enabled"
# Assign Cognitive Services OpenAI User role to service principal
echo "Assigning Cognitive Services OpenAI User role to service principal..."
- AI_SERVICES_ID=$(az cognitiveservices account show --name "$AI_SERVICES_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query id -o tsv)
+ AI_SERVICES_ID=$(az cognitiveservices account show --name "$AI_SERVICES_ACCOUNT" --resource-group "$AI_SERVICES_RG" --query id -o tsv)
az role assignment create --assignee-object-id "$SP_OBJECT_ID" --assignee-principal-type ServicePrincipal \
--role "Cognitive Services OpenAI User" --scope "$AI_SERVICES_ID" 2>/dev/null || echo "Role may already be assigned"
echo "โ
Cognitive Services OpenAI User role assigned"
else
- echo "โ ๏ธ No AI Services account found in resource group"
+ echo "โ ๏ธ No AI Services account found"
fi
# Get AI Search service and enable public access
@@ -334,9 +353,10 @@ jobs:
echo "โ ๏ธ No AI Search service found in resource group"
fi
- # Wait for network changes to propagate
- echo "โณ Waiting 60 seconds for network changes to propagate..."
- sleep 60
+ # Wait for network changes to propagate (increased to 2 minutes for Cosmos DB)
+ echo "โณ Waiting 2 minutes for network changes to propagate..."
+ sleep 120
+ echo "โ
Network changes should now be propagated"
- name: Run Post-Deployment Data Upload Scripts
shell: pwsh
From dac19a88a47880e671ea8cf5589992793128aa6f Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Tue, 27 Jan 2026 17:56:25 +0530
Subject: [PATCH 12/38] feat: Update deployment scripts to enhance AI Services
access management and support EXP mode
---
.github/workflows/job-deploy-linux.yml | 25 ++++++++++++++++----
.github/workflows/job-deploy-windows.yml | 29 +++++++++++++++++++-----
2 files changed, 44 insertions(+), 10 deletions(-)
diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml
index 814c920d..b43888da 100644
--- a/.github/workflows/job-deploy-linux.yml
+++ b/.github/workflows/job-deploy-linux.yml
@@ -407,8 +407,11 @@ jobs:
shell: bash
env:
INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ INPUT_EXP: ${{ inputs.EXP }}
+ INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
run: |
echo "๐ Restoring private network access settings..."
+ SUBSCRIPTION_ID=$(az account show --query id -o tsv 2>/dev/null)
# Get Cosmos DB account name
COSMOS_ACCOUNT=$(az cosmosdb list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
@@ -418,13 +421,27 @@ jobs:
echo "โ
Cosmos DB public access disabled"
fi
- # Get AI Services account name
+ # Get AI Services account - check current RG first, then EXP RG if needed
AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
+ AI_SERVICES_RG="$INPUT_RESOURCE_GROUP_NAME"
+
+ # If EXP mode and no AI Services in current RG, get from existing AI project RG
+ if [[ -z "$AI_SERVICES_ACCOUNT" && "$INPUT_EXP" == "true" ]]; then
+ if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
+ EXP_RG=$(echo "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ else
+ EXP_RG=$(echo "${{ vars.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ fi
+ if [[ -n "$EXP_RG" ]]; then
+ AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$EXP_RG" --query "[0].name" -o tsv 2>/dev/null)
+ AI_SERVICES_RG="$EXP_RG"
+ fi
+ fi
+
if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
- echo "Disabling public access for AI Services: $AI_SERVICES_ACCOUNT"
- SUBSCRIPTION_ID=$(az account show --query id -o tsv 2>/dev/null)
+ echo "Disabling public access for AI Services: $AI_SERVICES_ACCOUNT (RG: $AI_SERVICES_RG)"
az rest --method PATCH \
- --url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${INPUT_RESOURCE_GROUP_NAME}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
+ --url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${AI_SERVICES_RG}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
--body '{"properties":{"publicNetworkAccess":"Disabled"}}' || true
echo "โ
AI Services public access disabled"
fi
diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml
index ef0a48e8..851ee017 100644
--- a/.github/workflows/job-deploy-windows.yml
+++ b/.github/workflows/job-deploy-windows.yml
@@ -371,7 +371,7 @@ jobs:
$env:AZURE_PRINCIPAL_ID = $SP_OBJECT_ID
$env:AZURE_CLIENT_ID = "${{ secrets.AZURE_CLIENT_ID }}"
- bash ./$env:INFRA_PATH/scripts/data_scripts/run_upload_data_scripts.sh
+ bash ./infra/scripts/data_scripts/run_upload_data_scripts.sh
Write-Host "โ
Data upload completed successfully"
- name: Setup Python 3.11 for Agent Scripts
uses: actions/setup-python@v5
@@ -391,7 +391,7 @@ jobs:
$env:AZURE_PRINCIPAL_ID = $SP_OBJECT_ID
$env:AZURE_CLIENT_ID = "${{ secrets.AZURE_CLIENT_ID }}"
- bash ./$env:INFRA_PATH/scripts/agent_scripts/run_create_agents_scripts.sh
+ bash ./infra/scripts/agent_scripts/run_create_agents_scripts.sh
- name: Wait for Data Propagation
shell: bash
@@ -405,8 +405,11 @@ jobs:
shell: bash
env:
INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ INPUT_EXP: ${{ inputs.EXP }}
+ INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
run: |
echo "๐ Restoring private network access settings..."
+ SUBSCRIPTION_ID=$(az account show --query id -o tsv 2>/dev/null)
# Get Cosmos DB account name
COSMOS_ACCOUNT=$(az cosmosdb list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
@@ -416,13 +419,27 @@ jobs:
echo "โ
Cosmos DB public access disabled"
fi
- # Get AI Services account name
+ # Get AI Services account - check current RG first, then EXP RG if needed
AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
+ AI_SERVICES_RG="$INPUT_RESOURCE_GROUP_NAME"
+
+ # If EXP mode and no AI Services in current RG, get from existing AI project RG
+ if [[ -z "$AI_SERVICES_ACCOUNT" && "$INPUT_EXP" == "true" ]]; then
+ if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
+ EXP_RG=$(echo "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ else
+ EXP_RG=$(echo "${{ vars.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ fi
+ if [[ -n "$EXP_RG" ]]; then
+ AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$EXP_RG" --query "[0].name" -o tsv 2>/dev/null)
+ AI_SERVICES_RG="$EXP_RG"
+ fi
+ fi
+
if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
- echo "Disabling public access for AI Services: $AI_SERVICES_ACCOUNT"
- SUBSCRIPTION_ID=$(az account show --query id -o tsv 2>/dev/null)
+ echo "Disabling public access for AI Services: $AI_SERVICES_ACCOUNT (RG: $AI_SERVICES_RG)"
az rest --method PATCH \
- --url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${INPUT_RESOURCE_GROUP_NAME}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
+ --url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${AI_SERVICES_RG}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
--body '{"properties":{"publicNetworkAccess":"Disabled"}}' || true
echo "โ
AI Services public access disabled"
fi
From 98d604f69edd7dfd52cfe78dd70a4ddede6b788e Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Tue, 27 Jan 2026 18:17:40 +0530
Subject: [PATCH 13/38] fix: Simplify condition for cleanup-deployment job in
deployment orchestrator
---
.github/workflows/deploy-orchestrator.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/deploy-orchestrator.yml b/.github/workflows/deploy-orchestrator.yml
index 8e326abd..c4c08f11 100644
--- a/.github/workflows/deploy-orchestrator.yml
+++ b/.github/workflows/deploy-orchestrator.yml
@@ -126,7 +126,7 @@ jobs:
secrets: inherit
cleanup-deployment:
- if: "!cancelled() && needs.deploy.result == 'success' && needs.deploy.outputs.RESOURCE_GROUP_NAME != '' && inputs.existing_webapp_url == '' && (inputs.trigger_type != 'workflow_dispatch' || inputs.cleanup_resources)"
+ if: " needs.deploy.outputs.RESOURCE_GROUP_NAME != '' && inputs.existing_webapp_url == '' && (inputs.trigger_type != 'workflow_dispatch' || inputs.cleanup_resources)"
needs: [docker-build, deploy, e2e-test]
uses: ./.github/workflows/job-cleanup-deployment.yml
with:
From 30dd63a22820d005e49e10ce18c938813b8ee1a3 Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Tue, 27 Jan 2026 18:41:38 +0530
Subject: [PATCH 14/38] feat: Refactor AI Services account retrieval and role
assignment in deployment workflows
---
.github/workflows/job-deploy-linux.yml | 38 ++++++++++++++----------
.github/workflows/job-deploy-windows.yml | 38 ++++++++++++++----------
2 files changed, 44 insertions(+), 32 deletions(-)
diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml
index b43888da..860a0302 100644
--- a/.github/workflows/job-deploy-linux.yml
+++ b/.github/workflows/job-deploy-linux.yml
@@ -309,22 +309,26 @@ jobs:
echo "โ ๏ธ No Cosmos DB account found in resource group"
fi
- # Get AI Services account - check current RG first, then EXP RG if needed
- AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
+ # Get AI Services account - check current RG first, then extract from EXP project ID if needed
+ AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[?kind=='AIServices'].name | [0]" -o tsv)
AI_SERVICES_RG="$INPUT_RESOURCE_GROUP_NAME"
- # If EXP mode and no AI Services in current RG, get from existing AI project RG
+ # If EXP mode, extract AI Services account name directly from the project resource ID
if [[ -z "$AI_SERVICES_ACCOUNT" && "$INPUT_EXP" == "true" ]]; then
- echo "EXP mode: Looking for AI Services in existing AI project resource group..."
+ echo "EXP mode: Extracting AI Services from existing AI project resource ID..."
if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
- EXP_RG=$(echo "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ EXP_PROJECT_ID="$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID"
else
- EXP_RG=$(echo "${{ vars.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ EXP_PROJECT_ID="${{ vars.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}"
fi
- if [[ -n "$EXP_RG" ]]; then
- AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$EXP_RG" --query "[0].name" -o tsv)
+ # Extract resource group: .../resourceGroups/{rg}/...
+ EXP_RG=$(echo "$EXP_PROJECT_ID" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ # Extract account name: .../accounts/{accountName}/projects/...
+ EXP_ACCOUNT=$(echo "$EXP_PROJECT_ID" | sed -n 's|.*/accounts/\([^/]*\)/.*|\1|p')
+ if [[ -n "$EXP_ACCOUNT" && -n "$EXP_RG" ]]; then
+ AI_SERVICES_ACCOUNT="$EXP_ACCOUNT"
AI_SERVICES_RG="$EXP_RG"
- echo "Found AI Services in EXP resource group: $EXP_RG"
+ echo "Found AI Services from EXP project: $AI_SERVICES_ACCOUNT (RG: $AI_SERVICES_RG)"
fi
fi
@@ -335,12 +339,14 @@ jobs:
--body '{"properties":{"publicNetworkAccess":"Enabled"}}'
echo "โ
AI Services public access enabled"
- # Assign Cognitive Services OpenAI User role to service principal
- echo "Assigning Cognitive Services OpenAI User role to service principal..."
+ # Assign Cognitive Services roles to service principal for embeddings and OpenAI access
+ echo "Assigning Cognitive Services roles to service principal..."
AI_SERVICES_ID=$(az cognitiveservices account show --name "$AI_SERVICES_ACCOUNT" --resource-group "$AI_SERVICES_RG" --query id -o tsv)
az role assignment create --assignee-object-id "$SP_OBJECT_ID" --assignee-principal-type ServicePrincipal \
- --role "Cognitive Services OpenAI User" --scope "$AI_SERVICES_ID" 2>/dev/null || echo "Role may already be assigned"
- echo "โ
Cognitive Services OpenAI User role assigned"
+ --role "Cognitive Services OpenAI User" --scope "$AI_SERVICES_ID" 2>/dev/null || echo "OpenAI User role may already be assigned"
+ az role assignment create --assignee-object-id "$SP_OBJECT_ID" --assignee-principal-type ServicePrincipal \
+ --role "Cognitive Services User" --scope "$AI_SERVICES_ID" 2>/dev/null || echo "Cognitive Services User role may already be assigned"
+ echo "โ
Cognitive Services roles assigned"
else
echo "โ ๏ธ No AI Services account found"
fi
@@ -355,9 +361,9 @@ jobs:
echo "โ ๏ธ No AI Search service found in resource group"
fi
- # Wait for network changes to propagate (increased to 2 minutes for Cosmos DB)
- echo "โณ Waiting 2 minutes for network changes to propagate..."
- sleep 120
+ # Wait for network changes to propagate (3 minutes for Cosmos DB)
+ echo "โณ Waiting 3 minutes for network changes to propagate..."
+ sleep 180
echo "โ
Network changes should now be propagated"
- name: Run Data Upload Scripts
diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml
index 851ee017..6b2e30da 100644
--- a/.github/workflows/job-deploy-windows.yml
+++ b/.github/workflows/job-deploy-windows.yml
@@ -307,22 +307,26 @@ jobs:
echo "โ ๏ธ No Cosmos DB account found in resource group"
fi
- # Get AI Services account - check current RG first, then EXP RG if needed
- AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
+ # Get AI Services account - check current RG first, then extract from EXP project ID if needed
+ AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[?kind=='AIServices'].name | [0]" -o tsv)
AI_SERVICES_RG="$INPUT_RESOURCE_GROUP_NAME"
- # If EXP mode and no AI Services in current RG, get from existing AI project RG
+ # If EXP mode, extract AI Services account name directly from the project resource ID
if [[ -z "$AI_SERVICES_ACCOUNT" && "$INPUT_EXP" == "true" ]]; then
- echo "EXP mode: Looking for AI Services in existing AI project resource group..."
+ echo "EXP mode: Extracting AI Services from existing AI project resource ID..."
if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
- EXP_RG=$(echo "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ EXP_PROJECT_ID="$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID"
else
- EXP_RG=$(echo "${{ vars.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ EXP_PROJECT_ID="${{ vars.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}"
fi
- if [[ -n "$EXP_RG" ]]; then
- AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$EXP_RG" --query "[0].name" -o tsv)
+ # Extract resource group: .../resourceGroups/{rg}/...
+ EXP_RG=$(echo "$EXP_PROJECT_ID" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ # Extract account name: .../accounts/{accountName}/projects/...
+ EXP_ACCOUNT=$(echo "$EXP_PROJECT_ID" | sed -n 's|.*/accounts/\([^/]*\)/.*|\1|p')
+ if [[ -n "$EXP_ACCOUNT" && -n "$EXP_RG" ]]; then
+ AI_SERVICES_ACCOUNT="$EXP_ACCOUNT"
AI_SERVICES_RG="$EXP_RG"
- echo "Found AI Services in EXP resource group: $EXP_RG"
+ echo "Found AI Services from EXP project: $AI_SERVICES_ACCOUNT (RG: $AI_SERVICES_RG)"
fi
fi
@@ -333,12 +337,14 @@ jobs:
--body '{"properties":{"publicNetworkAccess":"Enabled"}}'
echo "โ
AI Services public access enabled"
- # Assign Cognitive Services OpenAI User role to service principal
- echo "Assigning Cognitive Services OpenAI User role to service principal..."
+ # Assign Cognitive Services roles to service principal for embeddings and OpenAI access
+ echo "Assigning Cognitive Services roles to service principal..."
AI_SERVICES_ID=$(az cognitiveservices account show --name "$AI_SERVICES_ACCOUNT" --resource-group "$AI_SERVICES_RG" --query id -o tsv)
az role assignment create --assignee-object-id "$SP_OBJECT_ID" --assignee-principal-type ServicePrincipal \
- --role "Cognitive Services OpenAI User" --scope "$AI_SERVICES_ID" 2>/dev/null || echo "Role may already be assigned"
- echo "โ
Cognitive Services OpenAI User role assigned"
+ --role "Cognitive Services OpenAI User" --scope "$AI_SERVICES_ID" 2>/dev/null || echo "OpenAI User role may already be assigned"
+ az role assignment create --assignee-object-id "$SP_OBJECT_ID" --assignee-principal-type ServicePrincipal \
+ --role "Cognitive Services User" --scope "$AI_SERVICES_ID" 2>/dev/null || echo "Cognitive Services User role may already be assigned"
+ echo "โ
Cognitive Services roles assigned"
else
echo "โ ๏ธ No AI Services account found"
fi
@@ -353,9 +359,9 @@ jobs:
echo "โ ๏ธ No AI Search service found in resource group"
fi
- # Wait for network changes to propagate (increased to 2 minutes for Cosmos DB)
- echo "โณ Waiting 2 minutes for network changes to propagate..."
- sleep 120
+ # Wait for network changes to propagate (3 minutes for Cosmos DB)
+ echo "โณ Waiting 3 minutes for network changes to propagate..."
+ sleep 180
echo "โ
Network changes should now be propagated"
- name: Run Post-Deployment Data Upload Scripts
From 0fac0b3007a25f796def2893b22e9276aa5e6c31 Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Wed, 28 Jan 2026 12:47:52 +0530
Subject: [PATCH 15/38] feat: Extend Cosmos DB connectivity checks and increase
wait time for network changes in deployment workflows
---
.github/workflows/job-deploy-linux.yml | 27 +++++++++++++++++++++---
.github/workflows/job-deploy-windows.yml | 27 +++++++++++++++++++++---
2 files changed, 48 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml
index 860a0302..78b78bb9 100644
--- a/.github/workflows/job-deploy-linux.yml
+++ b/.github/workflows/job-deploy-linux.yml
@@ -361,9 +361,30 @@ jobs:
echo "โ ๏ธ No AI Search service found in resource group"
fi
- # Wait for network changes to propagate (3 minutes for Cosmos DB)
- echo "โณ Waiting 3 minutes for network changes to propagate..."
- sleep 180
+ # Wait for network changes to propagate (5 minutes for Cosmos DB with private endpoints)
+ echo "โณ Waiting 5 minutes for Cosmos DB network changes to propagate..."
+ sleep 300
+
+ # Verify Cosmos DB is accessible before proceeding
+ if [[ -n "$COSMOS_ACCOUNT" ]]; then
+ echo "๐ Verifying Cosmos DB connectivity..."
+ COSMOS_ENDPOINT="https://${COSMOS_ACCOUNT}.documents.azure.com:443/"
+ MAX_RETRIES=10
+ RETRY_COUNT=0
+ while [[ $RETRY_COUNT -lt $MAX_RETRIES ]]; do
+ HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${COSMOS_ENDPOINT}" --max-time 10 2>/dev/null || echo "000")
+ if [[ "$HTTP_CODE" == "401" || "$HTTP_CODE" == "200" ]]; then
+ echo "โ
Cosmos DB is accessible (HTTP $HTTP_CODE - auth required, network OK)"
+ break
+ fi
+ RETRY_COUNT=$((RETRY_COUNT + 1))
+ echo "โณ Cosmos DB not yet accessible (HTTP $HTTP_CODE), retry $RETRY_COUNT/$MAX_RETRIES..."
+ sleep 30
+ done
+ if [[ $RETRY_COUNT -eq $MAX_RETRIES ]]; then
+ echo "โ ๏ธ Cosmos DB connectivity check timed out, proceeding anyway..."
+ fi
+ fi
echo "โ
Network changes should now be propagated"
- name: Run Data Upload Scripts
diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml
index 6b2e30da..d6a2bed6 100644
--- a/.github/workflows/job-deploy-windows.yml
+++ b/.github/workflows/job-deploy-windows.yml
@@ -359,9 +359,30 @@ jobs:
echo "โ ๏ธ No AI Search service found in resource group"
fi
- # Wait for network changes to propagate (3 minutes for Cosmos DB)
- echo "โณ Waiting 3 minutes for network changes to propagate..."
- sleep 180
+ # Wait for network changes to propagate (5 minutes for Cosmos DB with private endpoints)
+ echo "โณ Waiting 5 minutes for Cosmos DB network changes to propagate..."
+ sleep 300
+
+ # Verify Cosmos DB is accessible before proceeding
+ if [[ -n "$COSMOS_ACCOUNT" ]]; then
+ echo "๐ Verifying Cosmos DB connectivity..."
+ COSMOS_ENDPOINT="https://${COSMOS_ACCOUNT}.documents.azure.com:443/"
+ MAX_RETRIES=10
+ RETRY_COUNT=0
+ while [[ $RETRY_COUNT -lt $MAX_RETRIES ]]; do
+ HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${COSMOS_ENDPOINT}" --max-time 10 2>/dev/null || echo "000")
+ if [[ "$HTTP_CODE" == "401" || "$HTTP_CODE" == "200" ]]; then
+ echo "โ
Cosmos DB is accessible (HTTP $HTTP_CODE - auth required, network OK)"
+ break
+ fi
+ RETRY_COUNT=$((RETRY_COUNT + 1))
+ echo "โณ Cosmos DB not yet accessible (HTTP $HTTP_CODE), retry $RETRY_COUNT/$MAX_RETRIES..."
+ sleep 30
+ done
+ if [[ $RETRY_COUNT -eq $MAX_RETRIES ]]; then
+ echo "โ ๏ธ Cosmos DB connectivity check timed out, proceeding anyway..."
+ fi
+ fi
echo "โ
Network changes should now be propagated"
- name: Run Post-Deployment Data Upload Scripts
From 6d36958a6369ee939cd9581e29a5da7c9a3662f9 Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Wed, 28 Jan 2026 13:08:57 +0530
Subject: [PATCH 16/38] fix: Correct JSON file path in constants for E2E tests
---
tests/e2e-test/config/constants.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/e2e-test/config/constants.py b/tests/e2e-test/config/constants.py
index 84f74c08..bf2231c8 100644
--- a/tests/e2e-test/config/constants.py
+++ b/tests/e2e-test/config/constants.py
@@ -18,7 +18,7 @@
# Construct the absolute path to the JSON file
# note: may have to remove 'tests/e2e-test' from below when running locally
json_file_path = os.path.join(
- repo_root,"testdata", "golden_path_data.json"
+ repo_root,"tests/e2e-test","testdata", "golden_path_data.json"
)
# Load questions from JSON file
From e32bd1bf4b21036d24568f9bff75e2742d0416b2 Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Wed, 28 Jan 2026 13:19:13 +0530
Subject: [PATCH 17/38] fix: Update condition for cleanup-deployment job to
always execute
---
.github/workflows/deploy-orchestrator.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/deploy-orchestrator.yml b/.github/workflows/deploy-orchestrator.yml
index c4c08f11..33ffdf60 100644
--- a/.github/workflows/deploy-orchestrator.yml
+++ b/.github/workflows/deploy-orchestrator.yml
@@ -126,7 +126,7 @@ jobs:
secrets: inherit
cleanup-deployment:
- if: " needs.deploy.outputs.RESOURCE_GROUP_NAME != '' && inputs.existing_webapp_url == '' && (inputs.trigger_type != 'workflow_dispatch' || inputs.cleanup_resources)"
+ if: "always() && needs.deploy.outputs.RESOURCE_GROUP_NAME != '' && inputs.existing_webapp_url == '' && (inputs.trigger_type != 'workflow_dispatch' || inputs.cleanup_resources)"
needs: [docker-build, deploy, e2e-test]
uses: ./.github/workflows/job-cleanup-deployment.yml
with:
From 7c2b8d1d5aaee211a90a2cdab458c3f552977ddb Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Wed, 28 Jan 2026 14:10:08 +0530
Subject: [PATCH 18/38] feat: Enhance Cosmos DB access management by adding
runner IP to firewall and clearing IP rules on disable
---
.github/workflows/job-deploy-linux.yml | 14 ++++++++++++--
.github/workflows/job-deploy-windows.yml | 14 ++++++++++++--
2 files changed, 24 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml
index 78b78bb9..e354140d 100644
--- a/.github/workflows/job-deploy-linux.yml
+++ b/.github/workflows/job-deploy-linux.yml
@@ -304,7 +304,14 @@ jobs:
if [[ -n "$COSMOS_ACCOUNT" ]]; then
echo "Enabling public access for Cosmos DB: $COSMOS_ACCOUNT"
az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Enabled
- echo "โ
Cosmos DB public access enabled"
+
+ # Get GitHub Actions runner public IP and add to firewall
+ RUNNER_IP=$(curl -s https://api.ipify.org)
+ echo "GitHub Actions Runner IP: $RUNNER_IP"
+ echo "Adding runner IP to Cosmos DB firewall rules..."
+ az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" \
+ --ip-range-filter "$RUNNER_IP"
+ echo "โ
Cosmos DB public access enabled and runner IP added to firewall"
else
echo "โ ๏ธ No Cosmos DB account found in resource group"
fi
@@ -444,8 +451,11 @@ jobs:
COSMOS_ACCOUNT=$(az cosmosdb list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
if [[ -n "$COSMOS_ACCOUNT" ]]; then
echo "Disabling public access for Cosmos DB: $COSMOS_ACCOUNT"
+ # Clear IP rules first
+ az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --ip-range-filter "" || true
+ # Then disable public network access
az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Disabled || true
- echo "โ
Cosmos DB public access disabled"
+ echo "โ
Cosmos DB public access disabled and firewall rules cleared"
fi
# Get AI Services account - check current RG first, then EXP RG if needed
diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml
index d6a2bed6..9a56ac83 100644
--- a/.github/workflows/job-deploy-windows.yml
+++ b/.github/workflows/job-deploy-windows.yml
@@ -302,7 +302,14 @@ jobs:
if [[ -n "$COSMOS_ACCOUNT" ]]; then
echo "Enabling public access for Cosmos DB: $COSMOS_ACCOUNT"
az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Enabled
- echo "โ
Cosmos DB public access enabled"
+
+ # Get GitHub Actions runner public IP and add to firewall
+ RUNNER_IP=$(curl -s https://api.ipify.org)
+ echo "GitHub Actions Runner IP: $RUNNER_IP"
+ echo "Adding runner IP to Cosmos DB firewall rules..."
+ az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" \
+ --ip-range-filter "$RUNNER_IP"
+ echo "โ
Cosmos DB public access enabled and runner IP added to firewall"
else
echo "โ ๏ธ No Cosmos DB account found in resource group"
fi
@@ -442,8 +449,11 @@ jobs:
COSMOS_ACCOUNT=$(az cosmosdb list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
if [[ -n "$COSMOS_ACCOUNT" ]]; then
echo "Disabling public access for Cosmos DB: $COSMOS_ACCOUNT"
+ # Clear IP rules first
+ az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --ip-range-filter "" || true
+ # Then disable public network access
az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Disabled || true
- echo "โ
Cosmos DB public access disabled"
+ echo "โ
Cosmos DB public access disabled and firewall rules cleared"
fi
# Get AI Services account - check current RG first, then EXP RG if needed
From 3279ee0fcc6bc373be0cb0839307a2ff47803f0b Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Wed, 28 Jan 2026 16:31:17 +0530
Subject: [PATCH 19/38] refactor: Comment out run_e2e_tests configurations and
validations across deployment workflows
---
.github/workflows/deploy-linux.yml | 42 +++----
.github/workflows/deploy-orchestrator.yml | 44 +++----
.github/workflows/deploy-windows.yml | 42 +++----
.github/workflows/job-deploy.yml | 36 +++---
.github/workflows/job-send-notification.yml | 122 ++++++++++----------
5 files changed, 147 insertions(+), 139 deletions(-)
diff --git a/.github/workflows/deploy-linux.yml b/.github/workflows/deploy-linux.yml
index 3674f1c7..563919f9 100644
--- a/.github/workflows/deploy-linux.yml
+++ b/.github/workflows/deploy-linux.yml
@@ -52,15 +52,15 @@ on:
required: false
default: false
type: boolean
- run_e2e_tests:
- description: 'Run End-to-End Tests'
- required: false
- default: 'GoldenPath-Testing'
- type: choice
- options:
- - 'GoldenPath-Testing'
- - 'Smoke-Testing'
- - 'None'
+ # run_e2e_tests:
+ # description: 'Run End-to-End Tests'
+ # required: false
+ # default: 'GoldenPath-Testing'
+ # type: choice
+ # options:
+ # - 'GoldenPath-Testing'
+ # - 'Smoke-Testing'
+ # - 'None'
AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID:
description: 'Log Analytics Workspace ID (Optional)'
required: false
@@ -91,7 +91,7 @@ jobs:
exp: ${{ steps.validate.outputs.exp }}
build_docker_image: ${{ steps.validate.outputs.build_docker_image }}
cleanup_resources: ${{ steps.validate.outputs.cleanup_resources }}
- run_e2e_tests: ${{ steps.validate.outputs.run_e2e_tests }}
+ # run_e2e_tests: ${{ steps.validate.outputs.run_e2e_tests }}
azure_env_log_analytics_workspace_id: ${{ steps.validate.outputs.azure_env_log_analytics_workspace_id }}
azure_existing_ai_project_resource_id: ${{ steps.validate.outputs.azure_existing_ai_project_resource_id }}
existing_webapp_url: ${{ steps.validate.outputs.existing_webapp_url }}
@@ -106,7 +106,7 @@ jobs:
INPUT_EXP: ${{ github.event.inputs.EXP }}
INPUT_BUILD_DOCKER_IMAGE: ${{ github.event.inputs.build_docker_image }}
INPUT_CLEANUP_RESOURCES: ${{ github.event.inputs.cleanup_resources }}
- INPUT_RUN_E2E_TESTS: ${{ github.event.inputs.run_e2e_tests }}
+ # INPUT_RUN_E2E_TESTS: ${{ github.event.inputs.run_e2e_tests }}
INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ github.event.inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}
INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ github.event.inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
INPUT_EXISTING_WEBAPP_URL: ${{ github.event.inputs.existing_webapp_url }}
@@ -175,14 +175,14 @@ jobs:
echo "โ
cleanup_resources: '$CLEANUP_RESOURCES' is valid"
fi
- # Validate run_e2e_tests (specific allowed values)
- TEST_OPTION="${INPUT_RUN_E2E_TESTS:-GoldenPath-Testing}"
- if [[ "$TEST_OPTION" != "GoldenPath-Testing" && "$TEST_OPTION" != "Smoke-Testing" && "$TEST_OPTION" != "None" ]]; then
- echo "โ ERROR: run_e2e_tests must be one of: GoldenPath-Testing, Smoke-Testing, None, got: '$TEST_OPTION'"
- VALIDATION_FAILED=true
- else
- echo "โ
run_e2e_tests: '$TEST_OPTION' is valid"
- fi
+ # # Validate run_e2e_tests (specific allowed values)
+ # TEST_OPTION="${INPUT_RUN_E2E_TESTS:-GoldenPath-Testing}"
+ # if [[ "$TEST_OPTION" != "GoldenPath-Testing" && "$TEST_OPTION" != "Smoke-Testing" && "$TEST_OPTION" != "None" ]]; then
+ # echo "โ ERROR: run_e2e_tests must be one of: GoldenPath-Testing, Smoke-Testing, None, got: '$TEST_OPTION'"
+ # VALIDATION_FAILED=true
+ # else
+ # echo "โ
run_e2e_tests: '$TEST_OPTION' is valid"
+ # fi
# Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (optional, Azure Resource ID format)
if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then
@@ -242,7 +242,7 @@ jobs:
echo "exp=$EXP_ENABLED" >> $GITHUB_OUTPUT
echo "build_docker_image=$BUILD_DOCKER" >> $GITHUB_OUTPUT
echo "cleanup_resources=$CLEANUP_RESOURCES" >> $GITHUB_OUTPUT
- echo "run_e2e_tests=$TEST_OPTION" >> $GITHUB_OUTPUT
+ # echo "run_e2e_tests=$TEST_OPTION" >> $GITHUB_OUTPUT
echo "azure_env_log_analytics_workspace_id=$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" >> $GITHUB_OUTPUT
echo "azure_existing_ai_project_resource_id=$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" >> $GITHUB_OUTPUT
echo "existing_webapp_url=$INPUT_EXISTING_WEBAPP_URL" >> $GITHUB_OUTPUT
@@ -259,7 +259,7 @@ jobs:
EXP: ${{ needs.validate-inputs.outputs.exp == 'true' }}
build_docker_image: ${{ needs.validate-inputs.outputs.build_docker_image == 'true' }}
cleanup_resources: ${{ needs.validate-inputs.outputs.cleanup_resources == 'true' }}
- run_e2e_tests: ${{ needs.validate-inputs.outputs.run_e2e_tests }}
+ # run_e2e_tests: ${{ needs.validate-inputs.outputs.run_e2e_tests }}
AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ needs.validate-inputs.outputs.azure_env_log_analytics_workspace_id }}
AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ needs.validate-inputs.outputs.azure_existing_ai_project_resource_id }}
existing_webapp_url: ${{ needs.validate-inputs.outputs.existing_webapp_url }}
diff --git a/.github/workflows/deploy-orchestrator.yml b/.github/workflows/deploy-orchestrator.yml
index 33ffdf60..92a8a6e6 100644
--- a/.github/workflows/deploy-orchestrator.yml
+++ b/.github/workflows/deploy-orchestrator.yml
@@ -41,11 +41,11 @@ on:
required: false
default: false
type: boolean
- run_e2e_tests:
- description: 'Run End-to-End Tests'
- required: false
- default: 'GoldenPath-Testing'
- type: string
+ # run_e2e_tests:
+ # description: 'Run End-to-End Tests'
+ # required: false
+ # default: 'GoldenPath-Testing'
+ # type: string
AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID:
description: 'Log Analytics Workspace ID (Optional)'
required: false
@@ -93,41 +93,45 @@ jobs:
AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}
AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
docker_image_tag: ${{ needs.docker-build.outputs.IMAGE_TAG }}
- run_e2e_tests: ${{ inputs.run_e2e_tests }}
+ # run_e2e_tests: ${{ inputs.run_e2e_tests }}
cleanup_resources: ${{ inputs.cleanup_resources }}
secrets: inherit
- e2e-test:
- if: "!cancelled() && ((needs.deploy.result == 'success' && needs.deploy.outputs.CONTAINER_WEB_APPURL != '') || (inputs.existing_webapp_url != '' && inputs.existing_webapp_url != null)) && (inputs.trigger_type != 'workflow_dispatch' || (inputs.run_e2e_tests != 'None' && inputs.run_e2e_tests != '' && inputs.run_e2e_tests != null))"
- needs: [docker-build, deploy]
- uses: ./.github/workflows/test-automation-v2.yml
- with:
- TEST_URL: ${{ needs.deploy.outputs.CONTAINER_WEB_APPURL || inputs.existing_webapp_url }}
- TEST_SUITE: ${{ inputs.trigger_type == 'workflow_dispatch' && inputs.run_e2e_tests || 'GoldenPath-Testing' }}
- secrets: inherit
+ # e2e-test:
+ # if: "!cancelled() && ((needs.deploy.result == 'success' && needs.deploy.outputs.CONTAINER_WEB_APPURL != '') || (inputs.existing_webapp_url != '' && inputs.existing_webapp_url != null)) && (inputs.trigger_type != 'workflow_dispatch' || (inputs.run_e2e_tests != 'None' && inputs.run_e2e_tests != '' && inputs.run_e2e_tests != null))"
+ # needs: [docker-build, deploy]
+ # uses: ./.github/workflows/test-automation-v2.yml
+ # with:
+ # TEST_URL: ${{ needs.deploy.outputs.CONTAINER_WEB_APPURL || inputs.existing_webapp_url }}
+ # TEST_SUITE: ${{ inputs.trigger_type == 'workflow_dispatch' && inputs.run_e2e_tests || 'GoldenPath-Testing' }}
+ # secrets: inherit
send-notification:
if: "!cancelled()"
- needs: [docker-build, deploy, e2e-test]
+ needs: [docker-build, deploy]
+ # needs: [docker-build, deploy, e2e-test]
+
uses: ./.github/workflows/job-send-notification.yml
with:
trigger_type: ${{ inputs.trigger_type }}
waf_enabled: ${{ inputs.waf_enabled }}
EXP: ${{ inputs.EXP }}
- run_e2e_tests: ${{ inputs.run_e2e_tests }}
+ # run_e2e_tests: ${{ inputs.run_e2e_tests }}
existing_webapp_url: ${{ inputs.existing_webapp_url }}
deploy_result: ${{ needs.deploy.result }}
- e2e_test_result: ${{ needs.e2e-test.result }}
+ # e2e_test_result: ${{ needs.e2e-test.result }}
CONTAINER_WEB_APPURL: ${{ needs.deploy.outputs.CONTAINER_WEB_APPURL }}
RESOURCE_GROUP_NAME: ${{ needs.deploy.outputs.RESOURCE_GROUP_NAME }}
QUOTA_FAILED: ${{ needs.deploy.outputs.QUOTA_FAILED }}
- TEST_SUCCESS: ${{ needs.e2e-test.outputs.TEST_SUCCESS }}
- TEST_REPORT_URL: ${{ needs.e2e-test.outputs.TEST_REPORT_URL }}
+ # TEST_SUCCESS: ${{ needs.e2e-test.outputs.TEST_SUCCESS }}
+ # TEST_REPORT_URL: ${{ needs.e2e-test.outputs.TEST_REPORT_URL }}
secrets: inherit
cleanup-deployment:
if: "always() && needs.deploy.outputs.RESOURCE_GROUP_NAME != '' && inputs.existing_webapp_url == '' && (inputs.trigger_type != 'workflow_dispatch' || inputs.cleanup_resources)"
- needs: [docker-build, deploy, e2e-test]
+ needs: [docker-build, deploy]
+ # needs: [docker-build, deploy, e2e-test]
+
uses: ./.github/workflows/job-cleanup-deployment.yml
with:
runner_os: ${{ inputs.runner_os }}
diff --git a/.github/workflows/deploy-windows.yml b/.github/workflows/deploy-windows.yml
index 2987af50..b21e6d8b 100644
--- a/.github/workflows/deploy-windows.yml
+++ b/.github/workflows/deploy-windows.yml
@@ -47,15 +47,15 @@ on:
default: false
type: boolean
- run_e2e_tests:
- description: 'Run End-to-End Tests'
- required: false
- default: 'GoldenPath-Testing'
- type: choice
- options:
- - 'GoldenPath-Testing'
- - 'Smoke-Testing'
- - 'None'
+ # run_e2e_tests:
+ # description: 'Run End-to-End Tests'
+ # required: false
+ # default: 'GoldenPath-Testing'
+ # type: choice
+ # options:
+ # - 'GoldenPath-Testing'
+ # - 'Smoke-Testing'
+ # - 'None'
AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID:
description: 'Log Analytics Workspace ID (Optional)'
@@ -87,7 +87,7 @@ jobs:
exp: ${{ steps.validate.outputs.exp }}
build_docker_image: ${{ steps.validate.outputs.build_docker_image }}
cleanup_resources: ${{ steps.validate.outputs.cleanup_resources }}
- run_e2e_tests: ${{ steps.validate.outputs.run_e2e_tests }}
+ # run_e2e_tests: ${{ steps.validate.outputs.run_e2e_tests }}
azure_env_log_analytics_workspace_id: ${{ steps.validate.outputs.azure_env_log_analytics_workspace_id }}
azure_existing_ai_project_resource_id: ${{ steps.validate.outputs.azure_existing_ai_project_resource_id }}
existing_webapp_url: ${{ steps.validate.outputs.existing_webapp_url }}
@@ -102,7 +102,7 @@ jobs:
INPUT_EXP: ${{ github.event.inputs.EXP }}
INPUT_BUILD_DOCKER_IMAGE: ${{ github.event.inputs.build_docker_image }}
INPUT_CLEANUP_RESOURCES: ${{ github.event.inputs.cleanup_resources }}
- INPUT_RUN_E2E_TESTS: ${{ github.event.inputs.run_e2e_tests }}
+ # INPUT_RUN_E2E_TESTS: ${{ github.event.inputs.run_e2e_tests }}
INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ github.event.inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}
INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ github.event.inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
INPUT_EXISTING_WEBAPP_URL: ${{ github.event.inputs.existing_webapp_url }}
@@ -171,14 +171,14 @@ jobs:
echo "โ
cleanup_resources: '$CLEANUP_RESOURCES' is valid"
fi
- # Validate run_e2e_tests (specific allowed values)
- TEST_OPTION="${INPUT_RUN_E2E_TESTS:-GoldenPath-Testing}"
- if [[ "$TEST_OPTION" != "GoldenPath-Testing" && "$TEST_OPTION" != "Smoke-Testing" && "$TEST_OPTION" != "None" ]]; then
- echo "โ ERROR: run_e2e_tests must be one of: GoldenPath-Testing, Smoke-Testing, None, got: '$TEST_OPTION'"
- VALIDATION_FAILED=true
- else
- echo "โ
run_e2e_tests: '$TEST_OPTION' is valid"
- fi
+ # # Validate run_e2e_tests (specific allowed values)
+ # TEST_OPTION="${INPUT_RUN_E2E_TESTS:-GoldenPath-Testing}"
+ # if [[ "$TEST_OPTION" != "GoldenPath-Testing" && "$TEST_OPTION" != "Smoke-Testing" && "$TEST_OPTION" != "None" ]]; then
+ # echo "โ ERROR: run_e2e_tests must be one of: GoldenPath-Testing, Smoke-Testing, None, got: '$TEST_OPTION'"
+ # VALIDATION_FAILED=true
+ # else
+ # echo "โ
run_e2e_tests: '$TEST_OPTION' is valid"
+ # fi
# Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (optional, Azure Resource ID format)
if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then
@@ -238,7 +238,7 @@ jobs:
echo "exp=$EXP_ENABLED" >> $GITHUB_OUTPUT
echo "build_docker_image=$BUILD_DOCKER" >> $GITHUB_OUTPUT
echo "cleanup_resources=$CLEANUP_RESOURCES" >> $GITHUB_OUTPUT
- echo "run_e2e_tests=$TEST_OPTION" >> $GITHUB_OUTPUT
+ # echo "run_e2e_tests=$TEST_OPTION" >> $GITHUB_OUTPUT
echo "azure_env_log_analytics_workspace_id=$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" >> $GITHUB_OUTPUT
echo "azure_existing_ai_project_resource_id=$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" >> $GITHUB_OUTPUT
echo "existing_webapp_url=$INPUT_EXISTING_WEBAPP_URL" >> $GITHUB_OUTPUT
@@ -255,7 +255,7 @@ jobs:
EXP: ${{ needs.validate-inputs.outputs.exp == 'true' }}
build_docker_image: ${{ needs.validate-inputs.outputs.build_docker_image == 'true' }}
cleanup_resources: ${{ needs.validate-inputs.outputs.cleanup_resources == 'true' }}
- run_e2e_tests: ${{ needs.validate-inputs.outputs.run_e2e_tests }}
+ # run_e2e_tests: ${{ needs.validate-inputs.outputs.run_e2e_tests }}
AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ needs.validate-inputs.outputs.azure_env_log_analytics_workspace_id }}
AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ needs.validate-inputs.outputs.azure_existing_ai_project_resource_id }}
existing_webapp_url: ${{ needs.validate-inputs.outputs.existing_webapp_url }}
diff --git a/.github/workflows/job-deploy.yml b/.github/workflows/job-deploy.yml
index bcabd32c..be726813 100644
--- a/.github/workflows/job-deploy.yml
+++ b/.github/workflows/job-deploy.yml
@@ -44,11 +44,11 @@ on:
required: false
default: false
type: boolean
- run_e2e_tests:
- description: 'Run End-to-End Tests'
- required: false
- default: 'GoldenPath-Testing'
- type: string
+ # run_e2e_tests:
+ # description: 'Run End-to-End Tests'
+ # required: false
+ # default: 'GoldenPath-Testing'
+ # type: string
existing_webapp_url:
description: 'Existing Container WebApp URL (Skips Deployment)'
required: false
@@ -98,7 +98,7 @@ env:
WAF_ENABLED: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.waf_enabled || false) || false }}
EXP: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.EXP || false) || false }}
CLEANUP_RESOURCES: ${{ inputs.trigger_type != 'workflow_dispatch' || inputs.cleanup_resources }}
- RUN_E2E_TESTS: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.run_e2e_tests || 'GoldenPath-Testing') || 'GoldenPath-Testing' }}
+ # RUN_E2E_TESTS: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.run_e2e_tests || 'GoldenPath-Testing') || 'GoldenPath-Testing' }}
BUILD_DOCKER_IMAGE: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.build_docker_image || false) || false }}
jobs:
@@ -123,7 +123,7 @@ jobs:
INPUT_EXP: ${{ inputs.EXP }}
INPUT_WAF_ENABLED: ${{ inputs.waf_enabled }}
INPUT_CLEANUP_RESOURCES: ${{ inputs.cleanup_resources }}
- INPUT_RUN_E2E_TESTS: ${{ inputs.run_e2e_tests }}
+ # INPUT_RUN_E2E_TESTS: ${{ inputs.run_e2e_tests }}
INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}
INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
INPUT_DOCKER_IMAGE_TAG: ${{ inputs.docker_image_tag }}
@@ -178,16 +178,16 @@ jobs:
echo "โ
cleanup_resources: '$INPUT_CLEANUP_RESOURCES' is valid"
fi
- # Validate run_e2e_tests (specific allowed values)
- if [[ -n "$INPUT_RUN_E2E_TESTS" ]]; then
- ALLOWED_VALUES=("None" "GoldenPath-Testing" "Smoke-Testing")
- if [[ ! " ${ALLOWED_VALUES[@]} " =~ " ${INPUT_RUN_E2E_TESTS} " ]]; then
- echo "โ ERROR: run_e2e_tests '$INPUT_RUN_E2E_TESTS' is invalid. Allowed values: ${ALLOWED_VALUES[*]}"
- VALIDATION_FAILED=true
- else
- echo "โ
run_e2e_tests: '$INPUT_RUN_E2E_TESTS' is valid"
- fi
- fi
+ # # Validate run_e2e_tests (specific allowed values)
+ # if [[ -n "$INPUT_RUN_E2E_TESTS" ]]; then
+ # ALLOWED_VALUES=("None" "GoldenPath-Testing" "Smoke-Testing")
+ # if [[ ! " ${ALLOWED_VALUES[@]} " =~ " ${INPUT_RUN_E2E_TESTS} " ]]; then
+ # echo "โ ERROR: run_e2e_tests '$INPUT_RUN_E2E_TESTS' is invalid. Allowed values: ${ALLOWED_VALUES[*]}"
+ # VALIDATION_FAILED=true
+ # else
+ # echo "โ
run_e2e_tests: '$INPUT_RUN_E2E_TESTS' is valid"
+ # fi
+ # fi
# Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (Azure Resource ID format)
if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then
@@ -438,7 +438,7 @@ jobs:
echo "| **Branch** | \`${{ env.BRANCH_NAME }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| **WAF Enabled** | ${{ env.WAF_ENABLED == 'true' && 'โ
Yes' || 'โ No' }} |" >> $GITHUB_STEP_SUMMARY
echo "| **EXP Enabled** | ${{ env.EXP == 'true' && 'โ
Yes' || 'โ No' }} |" >> $GITHUB_STEP_SUMMARY
- echo "| **Run E2E Tests** | \`${{ env.RUN_E2E_TESTS }}\` |" >> $GITHUB_STEP_SUMMARY
+ # echo "| **Run E2E Tests** | \`${{ env.RUN_E2E_TESTS }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| **Cleanup Resources** | ${{ env.CLEANUP_RESOURCES == 'true' && 'โ
Yes' || 'โ No' }} |" >> $GITHUB_STEP_SUMMARY
echo "| **Build Docker Image** | ${{ env.BUILD_DOCKER_IMAGE == 'true' && 'โ
Yes' || 'โ No' }} |" >> $GITHUB_STEP_SUMMARY
diff --git a/.github/workflows/job-send-notification.yml b/.github/workflows/job-send-notification.yml
index c1f19df3..45ff78cf 100644
--- a/.github/workflows/job-send-notification.yml
+++ b/.github/workflows/job-send-notification.yml
@@ -19,11 +19,11 @@ on:
required: false
default: false
type: boolean
- run_e2e_tests:
- description: 'Run End-to-End Tests'
- required: false
- default: 'GoldenPath-Testing'
- type: string
+ # run_e2e_tests:
+ # description: 'Run End-to-End Tests'
+ # required: false
+ # default: 'GoldenPath-Testing'
+ # type: string
existing_webapp_url:
description: 'Existing Container WebApp URL (Skips Deployment)'
required: false
@@ -33,10 +33,10 @@ on:
description: 'Deploy job result (success, failure, skipped)'
required: true
type: string
- e2e_test_result:
- description: 'E2E test job result (success, failure, skipped)'
- required: true
- type: string
+ # e2e_test_result:
+ # description: 'E2E test job result (success, failure, skipped)'
+ # required: true
+ # type: string
CONTAINER_WEB_APPURL:
description: 'Container Web App URL'
required: false
@@ -68,7 +68,7 @@ env:
BRANCH_NAME: ${{ github.event.workflow_run.head_branch || github.head_ref || github.ref_name }}
WAF_ENABLED: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.waf_enabled || false) || false }}
EXP: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.EXP || false) || false }}
- RUN_E2E_TESTS: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.run_e2e_tests || 'GoldenPath-Testing') || 'GoldenPath-Testing' }}
+ # RUN_E2E_TESTS: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.run_e2e_tests || 'GoldenPath-Testing') || 'GoldenPath-Testing' }}
jobs:
send-notification:
@@ -83,10 +83,10 @@ jobs:
INPUT_TRIGGER_TYPE: ${{ inputs.trigger_type }}
INPUT_WAF_ENABLED: ${{ inputs.waf_enabled }}
INPUT_EXP: ${{ inputs.EXP }}
- INPUT_RUN_E2E_TESTS: ${{ inputs.run_e2e_tests }}
+ # INPUT_RUN_E2E_TESTS: ${{ inputs.run_e2e_tests }}
INPUT_EXISTING_WEBAPP_URL: ${{ inputs.existing_webapp_url }}
INPUT_DEPLOY_RESULT: ${{ inputs.deploy_result }}
- INPUT_E2E_TEST_RESULT: ${{ inputs.e2e_test_result }}
+ # INPUT_E2E_TEST_RESULT: ${{ inputs.e2e_test_result }}
INPUT_CONTAINER_WEB_APPURL: ${{ inputs.CONTAINER_WEB_APPURL }}
INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
INPUT_QUOTA_FAILED: ${{ inputs.QUOTA_FAILED }}
@@ -123,16 +123,16 @@ jobs:
echo "โ
EXP: '$INPUT_EXP' is valid"
fi
- # Validate run_e2e_tests (specific allowed values)
- if [[ -n "$INPUT_RUN_E2E_TESTS" ]]; then
- ALLOWED_VALUES=("None" "GoldenPath-Testing" "Smoke-Testing")
- if [[ ! " ${ALLOWED_VALUES[@]} " =~ " ${INPUT_RUN_E2E_TESTS} " ]]; then
- echo "โ ERROR: run_e2e_tests '$INPUT_RUN_E2E_TESTS' is invalid. Allowed values: ${ALLOWED_VALUES[*]}"
- VALIDATION_FAILED=true
- else
- echo "โ
run_e2e_tests: '$INPUT_RUN_E2E_TESTS' is valid"
- fi
- fi
+ # # Validate run_e2e_tests (specific allowed values)
+ # if [[ -n "$INPUT_RUN_E2E_TESTS" ]]; then
+ # ALLOWED_VALUES=("None" "GoldenPath-Testing" "Smoke-Testing")
+ # if [[ ! " ${ALLOWED_VALUES[@]} " =~ " ${INPUT_RUN_E2E_TESTS} " ]]; then
+ # echo "โ ERROR: run_e2e_tests '$INPUT_RUN_E2E_TESTS' is invalid. Allowed values: ${ALLOWED_VALUES[*]}"
+ # VALIDATION_FAILED=true
+ # else
+ # echo "โ
run_e2e_tests: '$INPUT_RUN_E2E_TESTS' is valid"
+ # fi
+ # fi
# Validate existing_webapp_url (must start with https if provided)
if [[ -n "$INPUT_EXISTING_WEBAPP_URL" ]]; then
@@ -158,19 +158,19 @@ jobs:
fi
fi
- # Validate e2e_test_result (required, must be specific values)
- if [[ -z "$INPUT_E2E_TEST_RESULT" ]]; then
- echo "โ ERROR: e2e_test_result is required but not provided"
- VALIDATION_FAILED=true
- else
- ALLOWED_TEST_RESULTS=("success" "failure" "skipped")
- if [[ ! " ${ALLOWED_TEST_RESULTS[@]} " =~ " ${INPUT_E2E_TEST_RESULT} " ]]; then
- echo "โ ERROR: e2e_test_result '$INPUT_E2E_TEST_RESULT' is invalid. Allowed values: ${ALLOWED_TEST_RESULTS[*]}"
- VALIDATION_FAILED=true
- else
- echo "โ
e2e_test_result: '$INPUT_E2E_TEST_RESULT' is valid"
- fi
- fi
+ # # Validate e2e_test_result (required, must be specific values)
+ # if [[ -z "$INPUT_E2E_TEST_RESULT" ]]; then
+ # echo "โ ERROR: e2e_test_result is required but not provided"
+ # VALIDATION_FAILED=true
+ # else
+ # ALLOWED_TEST_RESULTS=("success" "failure" "skipped")
+ # if [[ ! " ${ALLOWED_TEST_RESULTS[@]} " =~ " ${INPUT_E2E_TEST_RESULT} " ]]; then
+ # echo "โ ERROR: e2e_test_result '$INPUT_E2E_TEST_RESULT' is invalid. Allowed values: ${ALLOWED_TEST_RESULTS[*]}"
+ # VALIDATION_FAILED=true
+ # else
+ # echo "โ
e2e_test_result: '$INPUT_E2E_TEST_RESULT' is valid"
+ # fi
+ # fi
# Validate CONTAINER_WEB_APPURL (must start with https if provided)
if [[ -n "$INPUT_CONTAINER_WEB_APPURL" ]]; then
@@ -233,23 +233,23 @@ jobs:
echo ""
echo "โ
All input parameters validated successfully!"
- - name: Determine Test Suite Display Name
- id: test_suite
- shell: bash
- env:
- RUN_E2E_TESTS: ${{ env.RUN_E2E_TESTS }}
- run: |
- if [ "$RUN_E2E_TESTS" = "GoldenPath-Testing" ]; then
- TEST_SUITE_NAME="Golden Path Testing"
- elif [ "$RUN_E2E_TESTS" = "Smoke-Testing" ]; then
- TEST_SUITE_NAME="Smoke Testing"
- elif [ "$RUN_E2E_TESTS" = "None" ]; then
- TEST_SUITE_NAME="None"
- else
- TEST_SUITE_NAME="$RUN_E2E_TESTS"
- fi
- echo "TEST_SUITE_NAME=$TEST_SUITE_NAME" >> $GITHUB_OUTPUT
- echo "Test Suite: $TEST_SUITE_NAME"
+ # - name: Determine Test Suite Display Name
+ # id: test_suite
+ # shell: bash
+ # env:
+ # RUN_E2E_TESTS: ${{ env.RUN_E2E_TESTS }}
+ # run: |
+ # if [ "$RUN_E2E_TESTS" = "GoldenPath-Testing" ]; then
+ # TEST_SUITE_NAME="Golden Path Testing"
+ # elif [ "$RUN_E2E_TESTS" = "Smoke-Testing" ]; then
+ # TEST_SUITE_NAME="Smoke Testing"
+ # elif [ "$RUN_E2E_TESTS" = "None" ]; then
+ # TEST_SUITE_NAME="None"
+ # else
+ # TEST_SUITE_NAME="$RUN_E2E_TESTS"
+ # fi
+ # echo "TEST_SUITE_NAME=$TEST_SUITE_NAME" >> $GITHUB_OUTPUT
+ # echo "Test Suite: $TEST_SUITE_NAME"
- name: Send Quota Failure Notification
if: inputs.deploy_result == 'failure' && inputs.QUOTA_FAILED == 'true'
@@ -306,7 +306,7 @@ jobs:
INPUT_EXISTING_WEBAPP_URL: ${{ inputs.existing_webapp_url }}
INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
INPUT_TEST_REPORT_URL: ${{ inputs.TEST_REPORT_URL }}
- INPUT_E2E_TEST_RESULT: ${{ inputs.e2e_test_result }}
+ # INPUT_E2E_TEST_RESULT: ${{ inputs.e2e_test_result }}
ACCELERATOR_NAME: ${{ env.accelerator_name }}
LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}
GITHUB_REPOSITORY: ${{ github.repository }}
@@ -319,7 +319,7 @@ jobs:
WEBAPP_URL="${INPUT_CONTAINER_WEB_APPURL:-$INPUT_EXISTING_WEBAPP_URL}"
RESOURCE_GROUP="$INPUT_RESOURCE_GROUP_NAME"
TEST_REPORT_URL="$INPUT_TEST_REPORT_URL"
- TEST_SUITE_NAME="${{ steps.test_suite.outputs.TEST_SUITE_NAME }}"
+ # TEST_SUITE_NAME="${{ steps.test_suite.outputs.TEST_SUITE_NAME }}"
if [ "$INPUT_E2E_TEST_RESULT" = "skipped" ]; then
EMAIL_BODY=$(cat <
Date: Wed, 28 Jan 2026 18:16:26 +0530
Subject: [PATCH 20/38] fix: Update QUOTA_FAILED validation to allow empty
input and default to 'false'
---
.github/workflows/job-send-notification.yml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/job-send-notification.yml b/.github/workflows/job-send-notification.yml
index 45ff78cf..48f2bdf4 100644
--- a/.github/workflows/job-send-notification.yml
+++ b/.github/workflows/job-send-notification.yml
@@ -195,12 +195,12 @@ jobs:
fi
fi
- # Validate QUOTA_FAILED (must be 'true' or 'false')
- if [[ "$INPUT_QUOTA_FAILED" != "true" && "$INPUT_QUOTA_FAILED" != "false" ]]; then
- echo "โ ERROR: QUOTA_FAILED must be 'true' or 'false', got: '$INPUT_QUOTA_FAILED'"
+ # Validate QUOTA_FAILED (must be 'true' or 'false' or empty - defaults to 'false')
+ if [[ -n "$INPUT_QUOTA_FAILED" && "$INPUT_QUOTA_FAILED" != "true" && "$INPUT_QUOTA_FAILED" != "false" ]]; then
+ echo "โ ERROR: QUOTA_FAILED must be 'true', 'false', or empty, got: '$INPUT_QUOTA_FAILED'"
VALIDATION_FAILED=true
else
- echo "โ
QUOTA_FAILED: '$INPUT_QUOTA_FAILED' is valid"
+ echo "โ
QUOTA_FAILED: '${INPUT_QUOTA_FAILED:-false}' is valid"
fi
# Validate TEST_SUCCESS (must be 'true' or 'false' or empty)
From ffdc708b5f6cc90a140543f62095bb60847cd5d3 Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Wed, 28 Jan 2026 18:54:23 +0530
Subject: [PATCH 21/38] feat: Refactor data upload script to enhance parameter
handling and add public network access management
---
.../data_scripts/run_upload_data_scripts.sh | 366 ++++++++----------
1 file changed, 170 insertions(+), 196 deletions(-)
diff --git a/infra/scripts/data_scripts/run_upload_data_scripts.sh b/infra/scripts/data_scripts/run_upload_data_scripts.sh
index 3002f693..f14c4b61 100644
--- a/infra/scripts/data_scripts/run_upload_data_scripts.sh
+++ b/infra/scripts/data_scripts/run_upload_data_scripts.sh
@@ -1,81 +1,172 @@
#!/bin/bash
-echo "Starting the data upload script"
+set -e
+echo "Started the agent creation script setup..."
# Variables
+projectEndpoint="$1"
+solutionName="$2"
+gptModelName="$3"
+aiFoundryResourceId="$4"
+apiAppName="$5"
+resourceGroup="$6"
+searchEndpoint="$7"
-solutionName="$1"
-aiFoundryName="$2"
-backend_app_pid="$3"
-backend_app_uid="$4"
-app_service="$5"
-resource_group="$6"
-ai_search_endpoint="$7"
-azure_openai_endpoint="$8"
-embedding_model_name="${9}"
-aiFoundryResourceId="${10}"
-aiSearchResourceId="${11}"
-cosmosdb_account="${12}"
+original_foundry_public_access=""
# get parameters from azd env, if not provided
-if [ -z "$solutionName" ]; then
- solutionName=$(azd env get-value SOLUTION_NAME)
+if [ -z "$projectEndpoint" ]; then
+ projectEndpoint=$(azd env get-value AZURE_AI_AGENT_ENDPOINT)
fi
-if [ -z "$aiFoundryName" ]; then
- aiFoundryName=$(azd env get-value AI_SERVICE_NAME)
-fi
-
-if [ -z "$backend_app_pid" ]; then
- backend_app_pid=$(azd env get-value API_PID)
-fi
-
-if [ -z "$backend_app_uid" ]; then
- backend_app_uid=$(azd env get-value API_UID)
-fi
-
-if [ -z "$app_service" ]; then
- app_service=$(azd env get-value API_APP_NAME)
+if [ -z "$solutionName" ]; then
+ solutionName=$(azd env get-value SOLUTION_NAME)
fi
-if [ -z "$resource_group" ]; then
- resource_group=$(azd env get-value RESOURCE_GROUP_NAME)
+if [ -z "$gptModelName" ]; then
+ gptModelName=$(azd env get-value AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME)
fi
-if [ -z "$ai_search_endpoint" ]; then
- ai_search_endpoint=$(azd env get-value AZURE_AI_SEARCH_ENDPOINT)
-fi
-if [ -z "$azure_openai_endpoint" ]; then
- azure_openai_endpoint=$(azd env get-value AZURE_OPENAI_ENDPOINT)
+if [ -z "$aiFoundryResourceId" ]; then
+ aiFoundryResourceId=$(azd env get-value AI_FOUNDRY_RESOURCE_ID)
fi
-if [ -z "$embedding_model_name" ]; then
- embedding_model_name=$(azd env get-value AZURE_OPENAI_EMBEDDING_MODEL)
+if [ -z "$apiAppName" ]; then
+ apiAppName=$(azd env get-value API_APP_NAME)
fi
-if [ -z "$aiFoundryResourceId" ]; then
- aiFoundryResourceId=$(azd env get-value AI_FOUNDRY_RESOURCE_ID)
+if [ -z "$resourceGroup" ]; then
+ resourceGroup=$(azd env get-value AZURE_RESOURCE_GROUP)
fi
-if [ -z "$aiSearchResourceId" ]; then
- aiSearchResourceId=$(azd env get-value AI_SEARCH_SERVICE_RESOURCE_ID)
+if [ -z "$searchEndpoint" ]; then
+ searchEndpoint=$(azd env get-value AZURE_AI_SEARCH_ENDPOINT)
fi
-if [ -z "$cosmosdb_account" ]; then
- cosmosdb_account=$(azd env get-value AZURE_COSMOSDB_ACCOUNT)
+# Function to enable public network access temporarily
+enable_public_access() {
+ echo "=== Temporarily enabling public network access for services ==="
+ # Enable public access for AI Foundry
+ # Extract the account resource ID (remove /projects/... part if present)
+ aif_account_resource_id=$(echo "$aiFoundryResourceId" | sed 's|/projects/.*||')
+ aif_resource_name=$(basename "$aif_account_resource_id")
+ # Extract resource group from the AI Foundry account resource ID
+ aif_resource_group=$(echo "$aif_account_resource_id" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ # Extract subscription ID from the AI Foundry account resource ID
+ aif_subscription_id=$(echo "$aif_account_resource_id" | sed -n 's|.*/subscriptions/\([^/]*\)/.*|\1|p')
+
+ original_foundry_public_access=$(az cognitiveservices account show \
+ --name "$aif_resource_name" \
+ --resource-group "$aif_resource_group" \
+ --subscription "$aif_subscription_id" \
+ --query "properties.publicNetworkAccess" \
+ --output tsv)
+ if [ -z "$original_foundry_public_access" ] || [ "$original_foundry_public_access" = "null" ]; then
+ echo "โ Info: Could not retrieve AI Foundry network access status."
+ echo " AI Foundry network access might be managed differently."
+ elif [ "$original_foundry_public_access" != "Enabled" ]; then
+ echo "Current AI Foundry public access: $original_foundry_public_access"
+ echo "Enabling public access for AI Foundry resource: $aif_resource_name (Resource Group: $aif_resource_group)"
+ if MSYS_NO_PATHCONV=1 az resource update \
+ --ids "$aif_account_resource_id" \
+ --api-version 2024-10-01 \
+ --set properties.publicNetworkAccess=Enabled properties.apiProperties="{}" \
+ --output none; then
+ echo "โ AI Foundry public access enabled"
+ else
+ echo "โ Warning: Failed to enable AI Foundry public access automatically."
+ fi
+ else
+ echo "โ AI Foundry public access already enabled - no changes needed"
+ fi
+
+ # Wait for changes to take effect - Azure network changes can take 30-60 seconds to propagate
+ echo "Waiting for network access changes to propagate (this may take up to 60 seconds)..."
+ sleep 30
+
+ # Verify that public access is actually enabled by checking the current state
+ echo "Verifying public network access is enabled..."
+ current_access=$(az cognitiveservices account show \
+ --name "$aif_resource_name" \
+ --resource-group "$aif_resource_group" \
+ --subscription "$aif_subscription_id" \
+ --query "properties.publicNetworkAccess" \
+ --output tsv 2>/dev/null || echo "Unknown")
+
+ if [ "$current_access" = "Enabled" ]; then
+ echo "โ Verified: Public network access is enabled"
+ else
+ echo "โ Warning: Public access verification returned: $current_access"
+ echo " Waiting additional 30 seconds for propagation..."
+ sleep 30
+ fi
+
+ echo "=== Public network access enabled successfully ==="
+ return 0
+}
+
+# Function to restore original network access settings
+restore_network_access() {
+ echo "=== Restoring original network access settings ==="
+
+ # Restore AI Foundry access only if it was changed from the original state
+ if [ -n "$original_foundry_public_access" ] && [ "$original_foundry_public_access" != "Enabled" ]; then
+ echo "Restoring AI Foundry public access to: $original_foundry_public_access"
+ # Reconstruct the AI Foundry resource ID for restoration
+ aif_account_resource_id=$(echo "$aiFoundryResourceId" | sed 's|/projects/.*||')
+ # Try using the working approach to restore the original setting
+ if MSYS_NO_PATHCONV=1 az resource update \
+ --ids "$aif_account_resource_id" \
+ --api-version 2024-10-01 \
+ --set properties.publicNetworkAccess="$original_foundry_public_access" \
+ --set properties.apiProperties.qnaAzureSearchEndpointKey="" \
+ --set properties.networkAcls.bypass="AzureServices" \
+ --output none 2>/dev/null; then
+ echo "โ AI Foundry access restored"
+ else
+ echo "โ Warning: Failed to restore AI Foundry access automatically."
+ echo " Please manually restore network access in the Azure portal if needed."
+ fi
+ else
+ echo "AI Foundry access unchanged (no restoration needed)"
+ fi
+
+ echo "=== Network access restoration completed ==="
+}
+
+# Function to handle script cleanup on exit
+cleanup_on_exit() {
+ exit_code=$?
+ echo ""
+ if [ $exit_code -ne 0 ]; then
+ echo "Script failed with exit code: $exit_code"
+ fi
+ echo "Performing cleanup..."
+ restore_network_access
+ exit $exit_code
+}
+
+# Set up trap to ensure cleanup happens on exit
+trap cleanup_on_exit EXIT INT TERM
+
+# Check if all required arguments are provided
+if [ -z "$projectEndpoint" ] || [ -z "$solutionName" ] || [ -z "$gptModelName" ] || [ -z "$aiFoundryResourceId" ] || [ -z "$apiAppName" ] || [ -z "$resourceGroup" ] || [ -z "$searchEndpoint" ]; then
+ echo "Usage: $0 "
+ exit 1
fi
-
# Check if user is logged in to Azure
echo "Checking Azure authentication..."
if az account show &> /dev/null; then
echo "Already authenticated with Azure."
else
# Use Azure CLI login if running locally
- echo "Authenticating with Azure CLI..."
+ # echo "Authenticating with Azure CLI..."
az login
fi
echo "Getting principal id (user or service principal)"
+# Temporarily disable exit on error for principal detection
+set +e
# Try to get signed-in user first (for interactive logins)
signed_user_id=$(az ad signed-in-user show --query id -o tsv 2>/dev/null)
@@ -104,183 +195,66 @@ if [ -z "$signed_user_id" ]; then
else
echo "Logged in as user: $signed_user_id"
fi
+# Re-enable exit on error
+set -e
-echo "Checking if the principal has Search roles on the AI Search Service"
-# search service contributor role id: 7ca78c08-252a-4471-8644-bb5ff32d4ba0
-# search index data contributor role id: 8ebe5a00-799e-43f5-93ac-243d3dce84a7
-# search index data reader role id: 1407120a-92aa-4202-b7e9-c0e197c71c8f
+echo "Checking if the principal has Azure AI User role on the AI Foundry"
if [ "$SKIP_ROLE_ASSIGNMENT" != "true" ] && [ -n "$signed_user_id" ]; then
role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list \
- --role "7ca78c08-252a-4471-8644-bb5ff32d4ba0" \
- --scope "$aiSearchResourceId" \
+ --role "53ca6127-db72-4b80-b1b0-d745d6d5456d" \
+ --scope "$aiFoundryResourceId" \
--assignee "$signed_user_id" \
--query "[].roleDefinitionId" -o tsv)
if [ -z "$role_assignment" ]; then
- echo "Principal does not have the search service contributor role. Assigning the role..."
+ echo "Principal does not have the Azure AI User role. Assigning the role..."
MSYS_NO_PATHCONV=1 az role assignment create \
--assignee "$signed_user_id" \
- --role "7ca78c08-252a-4471-8644-bb5ff32d4ba0" \
- --scope "$aiSearchResourceId" \
+ --role "53ca6127-db72-4b80-b1b0-d745d6d5456d" \
+ --scope "$aiFoundryResourceId" \
--output none
if [ $? -eq 0 ]; then
- echo "Search service contributor role assigned successfully."
+ echo "Azure AI User role assigned successfully."
else
- echo "Failed to assign search service contributor role."
+ echo "Failed to assign Azure AI User role."
exit 1
fi
else
- echo "Principal already has the search service contributor role."
- fi
-
- role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list \
- --role "8ebe5a00-799e-43f5-93ac-243d3dce84a7" \
- --scope "$aiSearchResourceId" \
- --assignee "$signed_user_id" \
- --query "[].roleDefinitionId" -o tsv)
-
- if [ -z "$role_assignment" ]; then
- echo "Principal does not have the search index data contributor role. Assigning the role..."
- MSYS_NO_PATHCONV=1 az role assignment create \
- --assignee "$signed_user_id" \
- --role "8ebe5a00-799e-43f5-93ac-243d3dce84a7" \
- --scope "$aiSearchResourceId" \
- --output none
-
- if [ $? -eq 0 ]; then
- echo "Search index data contributor role assigned successfully."
- else
- echo "Failed to assign search index data contributor role."
- exit 1
- fi
- else
- echo "Principal already has the search index data contributor role."
+ echo "Principal already has the Azure AI User role."
fi
else
echo "Skipping role assignment (will rely on existing permissions)"
fi
-if [ "$SKIP_ROLE_ASSIGNMENT" != "true" ] && [ -n "$signed_user_id" ]; then
- role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list \
- --role "1407120a-92aa-4202-b7e9-c0e197c71c8f" \
- --scope "$aiSearchResourceId" \
- --assignee "$signed_user_id" \
- --query "[].roleDefinitionId" -o tsv)
- if [ -z "$role_assignment" ]; then
- echo "Principal does not have the search index data reader role. Assigning the role..."
- MSYS_NO_PATHCONV=1 az role assignment create \
- --assignee "$signed_user_id" \
- --role "1407120a-92aa-4202-b7e9-c0e197c71c8f" \
- --scope "$aiSearchResourceId" \
- --output none
+requirementFile="infra/scripts/agent_scripts/requirements.txt"
- if [ $? -eq 0 ]; then
- echo "Search index data reader role assigned successfully."
- else
- echo "Failed to assign search index data reader role."
- exit 1
- fi
- else
- echo "Principal already has the search index data reader role."
- fi
-fi
-
-# Check if the principal has the Cosmos DB Built-in Data Contributor role
-if [ "$SKIP_ROLE_ASSIGNMENT" != "true" ] && [ -n "$signed_user_id" ]; then
- echo "Checking if principal has the Cosmos DB Built-in Data Contributor role"
- roleExists=$(az cosmosdb sql role assignment list \
- --resource-group $resource_group \
- --account-name $cosmosdb_account \
- --query "[?roleDefinitionId.ends_with(@, '00000000-0000-0000-0000-000000000002') && principalId == '$signed_user_id']" -o tsv)
+# Download and install Python requirements
+python -m pip install --upgrade pip
+python -m pip install --quiet -r "$requirementFile"
- # Check if the role exists
- if [ -n "$roleExists" ]; then
- echo "Principal already has the Cosmos DB Built-in Data Contributor role."
- else
- echo "Principal does not have the Cosmos DB Built-in Data Contributor role. Assigning the role."
- MSYS_NO_PATHCONV=1 az cosmosdb sql role assignment create \
- --resource-group $resource_group \
- --account-name $cosmosdb_account \
- --role-definition-id 00000000-0000-0000-0000-000000000002 \
- --principal-id $signed_user_id \
- --scope "/" \
- --output none
- if [ $? -eq 0 ]; then
- echo "Cosmos DB Built-in Data Contributor role assigned successfully."
- else
- echo "Failed to assign Cosmos DB Built-in Data Contributor role."
- fi
- fi
-else
- echo "Skipping Cosmos DB role assignment (will rely on existing permissions)"
+# Enable public network access for required services
+enable_public_access
+if [ $? -ne 0 ]; then
+ echo "Error: Failed to enable public network access for services."
+ exit 1
fi
-# role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list \
-# --role "00000000-0000-0000-0000-000000000002" \
-# --scope "$cosmosdbAccountId" \
-# --assignee "$signed_user_id" \
-# --query "[].roleDefinitionId" -o tsv)
-
-# if [ -z "$role_assignment" ]; then
-# echo "User does not have the Cosmos DB account contributor role. Assigning the role..."
-# MSYS_NO_PATHCONV=1 az role assignment create \
-# --assignee "$signed_user_id" \
-# --role "00000000-0000-0000-0000-000000000002" \
-# --scope "$cosmosdbAccountId" \
-# --output none
-
-# if [ $? -eq 0 ]; then
-# echo "Cosmos DB account contributor role assigned successfully."
-# else
-# echo "Failed to assign Cosmos DB account contributor role."
-# exit 1
-# fi
-# else
-# echo "User already has the Cosmos DB account contributor role."
-# fi
-
-# role_assignment=$(MSYS_NO_PATHCONV=1 az cosmosdb sql role assignment list \
-# --account-name "$cosmosdb_account" \
-# --resource-group "$resource_group" \
-# --scope "$cosmosdbAccountId" \
-# --principal-id "$signed_user_id" \
-# --query "[].roleDefinitionId" -o tsv)
+# Execute the Python scripts
+echo "Running Python agents creation script..."
+python_output=$(python infra/scripts/agent_scripts/01_create_agents.py --ai_project_endpoint="$projectEndpoint" --solution_name="$solutionName" --gpt_model_name="$gptModelName" --ai_search_endpoint="$searchEndpoint")
+eval $(echo "$python_output" | grep -E "^(chatAgentId|productAgentId|policyAgentId)=")
-# if [ -z "$role_assignment" ]; then
-# echo "User does not have the Cosmos DB SQL role. Assigning the role..."
-# MSYS_NO_PATHCONV=1 az cosmosdb sql role assignment create \
-# --account-name "$cosmosdb_account" \
-# --resource-group "$resource_group" \
-# --principal-id "$signed_user_id" \
-# --role-definition-id "00000000-0000-0000-0000-000000000002" \
-# --scope "$cosmosdbAccountId" \
-# --output none
-
-# if [ $? -eq 0 ]; then
-# echo "Cosmos DB SQL role assigned successfully."
-# else
-# echo "Failed to assign Cosmos DB SQL role."
-# exit 1
-# fi
-# else
-# echo "User already has the Cosmos DB SQL role."
-# fi
-
-# python -m venv .venv
-
-# .venv\Scripts\activate
-
-requirementFile="infra/scripts/data_scripts/requirements.txt"
-
-# Download and install Python requirements
-python -m pip install --upgrade pip
-python -m pip install --quiet -r "$requirementFile"
+echo "Agents creation completed."
+# Update environment variables of API App
+az webapp config appsettings set \
+ --resource-group "$resourceGroup" \
+ --name "$apiAppName" \
+ --settings FOUNDRY_CHAT_AGENT_ID="$chatAgentId" FOUNDRY_CUSTOM_PRODUCT_AGENT_ID="$productAgentId" FOUNDRY_POLICY_AGENT_ID="$policyAgentId" \
+ -o none
-# python pip install -r infra/scripts/data_scripts/requirements.txt --quiet
-python infra/scripts/data_scripts/01_create_products_search_index.py --ai_search_endpoint="$ai_search_endpoint" --azure_openai_endpoint="$azure_openai_endpoint" --embedding_model_name="$embedding_model_name"
-python infra/scripts/data_scripts/02_create_policies_search_index.py --ai_search_endpoint="$ai_search_endpoint" --azure_openai_endpoint="$azure_openai_endpoint" --embedding_model_name="$embedding_model_name"
-python infra/scripts/data_scripts/03_write_products_to_cosmos.py --cosmosdb_account="$cosmosdb_account"
\ No newline at end of file
+echo "Environment variables updated for App Service: $apiAppName"
+echo "Network access will be restored to original settings..."
\ No newline at end of file
From deca2f6db4af1e99914a0035594828e57a77f738 Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Wed, 28 Jan 2026 19:05:13 +0530
Subject: [PATCH 22/38] fix: Adjust formatting of enable_public_access function
for consistency
---
infra/scripts/data_scripts/run_upload_data_scripts.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/infra/scripts/data_scripts/run_upload_data_scripts.sh b/infra/scripts/data_scripts/run_upload_data_scripts.sh
index f14c4b61..e0b2ca89 100644
--- a/infra/scripts/data_scripts/run_upload_data_scripts.sh
+++ b/infra/scripts/data_scripts/run_upload_data_scripts.sh
@@ -43,7 +43,7 @@ if [ -z "$searchEndpoint" ]; then
fi
# Function to enable public network access temporarily
-enable_public_access() {
+enable_public_access(){
echo "=== Temporarily enabling public network access for services ==="
# Enable public access for AI Foundry
# Extract the account resource ID (remove /projects/... part if present)
From 075b42e6e73b88f4666f37b502f8569b224a6138 Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Wed, 28 Jan 2026 19:19:35 +0530
Subject: [PATCH 23/38] feat: Refactor data upload script to improve parameter
handling and role assignments for Azure services
---
.../run_create_agents_scripts.sh | 168 +++++++-
.../data_scripts/run_upload_data_scripts.sh | 366 ++++++++++--------
2 files changed, 349 insertions(+), 185 deletions(-)
diff --git a/infra/scripts/agent_scripts/run_create_agents_scripts.sh b/infra/scripts/agent_scripts/run_create_agents_scripts.sh
index 4c720c34..d5092d29 100644
--- a/infra/scripts/agent_scripts/run_create_agents_scripts.sh
+++ b/infra/scripts/agent_scripts/run_create_agents_scripts.sh
@@ -11,35 +11,153 @@ apiAppName="$5"
resourceGroup="$6"
searchEndpoint="$7"
+original_foundry_public_access=""
+SKIP_ROLE_ASSIGNMENT=false
+
+# Helper function to get azd env value safely (works in both local and pipeline)
+get_azd_value() {
+ local key="$1"
+ if command -v azd &> /dev/null; then
+ azd env get-value "$key" 2>/dev/null || echo ""
+ else
+ echo ""
+ fi
+}
+
# get parameters from azd env, if not provided
if [ -z "$projectEndpoint" ]; then
- projectEndpoint=$(azd env get-value AZURE_AI_AGENT_ENDPOINT)
+ projectEndpoint=$(get_azd_value AZURE_AI_AGENT_ENDPOINT)
fi
if [ -z "$solutionName" ]; then
- solutionName=$(azd env get-value SOLUTION_NAME)
+ solutionName=$(get_azd_value SOLUTION_NAME)
fi
if [ -z "$gptModelName" ]; then
- gptModelName=$(azd env get-value AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME)
+ gptModelName=$(get_azd_value AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME)
fi
if [ -z "$aiFoundryResourceId" ]; then
- aiFoundryResourceId=$(azd env get-value AI_FOUNDRY_RESOURCE_ID)
+ aiFoundryResourceId=$(get_azd_value AI_FOUNDRY_RESOURCE_ID)
fi
if [ -z "$apiAppName" ]; then
- apiAppName=$(azd env get-value API_APP_NAME)
+ apiAppName=$(get_azd_value API_APP_NAME)
fi
if [ -z "$resourceGroup" ]; then
- resourceGroup=$(azd env get-value AZURE_RESOURCE_GROUP)
+ resourceGroup=$(get_azd_value AZURE_RESOURCE_GROUP)
fi
if [ -z "$searchEndpoint" ]; then
- searchEndpoint=$(azd env get-value AZURE_AI_SEARCH_ENDPOINT)
+ searchEndpoint=$(get_azd_value AZURE_AI_SEARCH_ENDPOINT)
fi
+# Function to enable public network access temporarily
+enable_public_access() {
+ echo "=== Temporarily enabling public network access for services ==="
+ # Enable public access for AI Foundry
+ # Extract the account resource ID (remove /projects/... part if present)
+ aif_account_resource_id=$(echo "$aiFoundryResourceId" | sed 's|/projects/.*||')
+ aif_resource_name=$(basename "$aif_account_resource_id")
+ # Extract resource group from the AI Foundry account resource ID
+ aif_resource_group=$(echo "$aif_account_resource_id" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ # Extract subscription ID from the AI Foundry account resource ID
+ aif_subscription_id=$(echo "$aif_account_resource_id" | sed -n 's|.*/subscriptions/\([^/]*\)/.*|\1|p')
+
+ original_foundry_public_access=$(az cognitiveservices account show \
+ --name "$aif_resource_name" \
+ --resource-group "$aif_resource_group" \
+ --subscription "$aif_subscription_id" \
+ --query "properties.publicNetworkAccess" \
+ --output tsv)
+ if [ -z "$original_foundry_public_access" ] || [ "$original_foundry_public_access" = "null" ]; then
+ echo "โ Info: Could not retrieve AI Foundry network access status."
+ echo " AI Foundry network access might be managed differently."
+ elif [ "$original_foundry_public_access" != "Enabled" ]; then
+ echo "Current AI Foundry public access: $original_foundry_public_access"
+ echo "Enabling public access for AI Foundry resource: $aif_resource_name (Resource Group: $aif_resource_group)"
+ if MSYS_NO_PATHCONV=1 az resource update \
+ --ids "$aif_account_resource_id" \
+ --api-version 2024-10-01 \
+ --set properties.publicNetworkAccess=Enabled properties.apiProperties="{}" \
+ --output none; then
+ echo "โ AI Foundry public access enabled"
+ else
+ echo "โ Warning: Failed to enable AI Foundry public access automatically."
+ fi
+ else
+ echo "โ AI Foundry public access already enabled - no changes needed"
+ fi
+
+ # Wait for changes to take effect - Azure network changes can take 30-60 seconds to propagate
+ echo "Waiting for network access changes to propagate (this may take up to 60 seconds)..."
+ sleep 30
+
+ # Verify that public access is actually enabled by checking the current state
+ echo "Verifying public network access is enabled..."
+ current_access=$(az cognitiveservices account show \
+ --name "$aif_resource_name" \
+ --resource-group "$aif_resource_group" \
+ --subscription "$aif_subscription_id" \
+ --query "properties.publicNetworkAccess" \
+ --output tsv 2>/dev/null || echo "Unknown")
+
+ if [ "$current_access" = "Enabled" ]; then
+ echo "โ Verified: Public network access is enabled"
+ else
+ echo "โ Warning: Public access verification returned: $current_access"
+ echo " Waiting additional 30 seconds for propagation..."
+ sleep 30
+ fi
+
+ echo "=== Public network access enabled successfully ==="
+ return 0
+}
+
+# Function to restore original network access settings
+restore_network_access() {
+ echo "=== Restoring original network access settings ==="
+
+ # Restore AI Foundry access only if it was changed from the original state
+ if [ -n "$original_foundry_public_access" ] && [ "$original_foundry_public_access" != "Enabled" ]; then
+ echo "Restoring AI Foundry public access to: $original_foundry_public_access"
+ # Reconstruct the AI Foundry resource ID for restoration
+ aif_account_resource_id=$(echo "$aiFoundryResourceId" | sed 's|/projects/.*||')
+ # Try using the working approach to restore the original setting
+ if MSYS_NO_PATHCONV=1 az resource update \
+ --ids "$aif_account_resource_id" \
+ --api-version 2024-10-01 \
+ --set properties.publicNetworkAccess="$original_foundry_public_access" \
+ --set properties.apiProperties.qnaAzureSearchEndpointKey="" \
+ --set properties.networkAcls.bypass="AzureServices" \
+ --output none 2>/dev/null; then
+ echo "โ AI Foundry access restored"
+ else
+ echo "โ Warning: Failed to restore AI Foundry access automatically."
+ echo " Please manually restore network access in the Azure portal if needed."
+ fi
+ else
+ echo "AI Foundry access unchanged (no restoration needed)"
+ fi
+
+ echo "=== Network access restoration completed ==="
+}
+
+# Function to handle script cleanup on exit
+cleanup_on_exit() {
+ exit_code=$?
+ echo ""
+ if [ $exit_code -ne 0 ]; then
+ echo "Script failed with exit code: $exit_code"
+ fi
+ echo "Performing cleanup..."
+ restore_network_access
+ exit $exit_code
+}
+
+# Set up trap to ensure cleanup happens on exit
+trap cleanup_on_exit EXIT INT TERM
# Check if all required arguments are provided
if [ -z "$projectEndpoint" ] || [ -z "$solutionName" ] || [ -z "$gptModelName" ] || [ -z "$aiFoundryResourceId" ] || [ -z "$apiAppName" ] || [ -z "$resourceGroup" ] || [ -z "$searchEndpoint" ]; then
@@ -66,11 +184,12 @@ signed_user_id=$(az ad signed-in-user show --query id -o tsv 2>/dev/null)
# If that fails, we're likely using a service principal - get its object ID
if [ -z "$signed_user_id" ]; then
echo "Not logged in as user, checking for service principal..."
- account_info=$(az account show --query '{name:user.name, type:user.type}' -o json)
- account_type=$(echo "$account_info" | jq -r '.type')
+
+ # Get account type - use jq if available, otherwise use grep/sed
+ account_type=$(az account show --query 'user.type' -o tsv 2>/dev/null)
if [ "$account_type" = "servicePrincipal" ]; then
- sp_name=$(echo "$account_info" | jq -r '.name')
+ sp_name=$(az account show --query 'user.name' -o tsv 2>/dev/null)
echo "Logged in as service principal: $sp_name"
signed_user_id=$(az ad sp show --id "$sp_name" --query id -o tsv 2>/dev/null)
@@ -82,7 +201,7 @@ if [ -z "$signed_user_id" ]; then
echo "Service principal object ID: $signed_user_id"
fi
else
- echo "Warning: Could not determine principal ID. Attempting to continue without role assignment..."
+ echo "Warning: Could not determine principal ID (type: $account_type). Attempting to continue without role assignment..."
SKIP_ROLE_ASSIGNMENT=true
fi
else
@@ -124,13 +243,31 @@ fi
requirementFile="infra/scripts/agent_scripts/requirements.txt"
+# Determine python command (python3 preferred, fallback to python)
+if command -v python3 &> /dev/null; then
+ PYTHON_CMD="python3"
+elif command -v python &> /dev/null; then
+ PYTHON_CMD="python"
+else
+ echo "Error: Python is not installed or not in PATH"
+ exit 1
+fi
+echo "Using Python command: $PYTHON_CMD"
+
# Download and install Python requirements
-python -m pip install --upgrade pip
-python -m pip install --quiet -r "$requirementFile"
+$PYTHON_CMD -m pip install --upgrade pip
+$PYTHON_CMD -m pip install --quiet -r "$requirementFile"
+
+# Enable public network access for required services
+enable_public_access
+if [ $? -ne 0 ]; then
+ echo "Error: Failed to enable public network access for services."
+ exit 1
+fi
# Execute the Python scripts
echo "Running Python agents creation script..."
-python_output=$(python infra/scripts/agent_scripts/01_create_agents.py --ai_project_endpoint="$projectEndpoint" --solution_name="$solutionName" --gpt_model_name="$gptModelName" --ai_search_endpoint="$searchEndpoint")
+python_output=$($PYTHON_CMD infra/scripts/agent_scripts/01_create_agents.py --ai_project_endpoint="$projectEndpoint" --solution_name="$solutionName" --gpt_model_name="$gptModelName" --ai_search_endpoint="$searchEndpoint")
eval $(echo "$python_output" | grep -E "^(chatAgentId|productAgentId|policyAgentId)=")
echo "Agents creation completed."
@@ -142,4 +279,5 @@ az webapp config appsettings set \
--settings FOUNDRY_CHAT_AGENT_ID="$chatAgentId" FOUNDRY_CUSTOM_PRODUCT_AGENT_ID="$productAgentId" FOUNDRY_POLICY_AGENT_ID="$policyAgentId" \
-o none
-echo "Environment variables updated for App Service: $apiAppName"
\ No newline at end of file
+echo "Environment variables updated for App Service: $apiAppName"
+echo "Network access will be restored to original settings..."
\ No newline at end of file
diff --git a/infra/scripts/data_scripts/run_upload_data_scripts.sh b/infra/scripts/data_scripts/run_upload_data_scripts.sh
index e0b2ca89..3002f693 100644
--- a/infra/scripts/data_scripts/run_upload_data_scripts.sh
+++ b/infra/scripts/data_scripts/run_upload_data_scripts.sh
@@ -1,172 +1,81 @@
#!/bin/bash
-set -e
-echo "Started the agent creation script setup..."
+echo "Starting the data upload script"
# Variables
-projectEndpoint="$1"
-solutionName="$2"
-gptModelName="$3"
-aiFoundryResourceId="$4"
-apiAppName="$5"
-resourceGroup="$6"
-searchEndpoint="$7"
-original_foundry_public_access=""
+solutionName="$1"
+aiFoundryName="$2"
+backend_app_pid="$3"
+backend_app_uid="$4"
+app_service="$5"
+resource_group="$6"
+ai_search_endpoint="$7"
+azure_openai_endpoint="$8"
+embedding_model_name="${9}"
+aiFoundryResourceId="${10}"
+aiSearchResourceId="${11}"
+cosmosdb_account="${12}"
# get parameters from azd env, if not provided
-if [ -z "$projectEndpoint" ]; then
- projectEndpoint=$(azd env get-value AZURE_AI_AGENT_ENDPOINT)
-fi
-
if [ -z "$solutionName" ]; then
solutionName=$(azd env get-value SOLUTION_NAME)
fi
-if [ -z "$gptModelName" ]; then
- gptModelName=$(azd env get-value AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME)
+if [ -z "$aiFoundryName" ]; then
+ aiFoundryName=$(azd env get-value AI_SERVICE_NAME)
fi
-if [ -z "$aiFoundryResourceId" ]; then
- aiFoundryResourceId=$(azd env get-value AI_FOUNDRY_RESOURCE_ID)
+if [ -z "$backend_app_pid" ]; then
+ backend_app_pid=$(azd env get-value API_PID)
+fi
+
+if [ -z "$backend_app_uid" ]; then
+ backend_app_uid=$(azd env get-value API_UID)
+fi
+
+if [ -z "$app_service" ]; then
+ app_service=$(azd env get-value API_APP_NAME)
+fi
+
+if [ -z "$resource_group" ]; then
+ resource_group=$(azd env get-value RESOURCE_GROUP_NAME)
fi
-if [ -z "$apiAppName" ]; then
- apiAppName=$(azd env get-value API_APP_NAME)
+if [ -z "$ai_search_endpoint" ]; then
+ ai_search_endpoint=$(azd env get-value AZURE_AI_SEARCH_ENDPOINT)
+fi
+if [ -z "$azure_openai_endpoint" ]; then
+ azure_openai_endpoint=$(azd env get-value AZURE_OPENAI_ENDPOINT)
fi
-if [ -z "$resourceGroup" ]; then
- resourceGroup=$(azd env get-value AZURE_RESOURCE_GROUP)
+if [ -z "$embedding_model_name" ]; then
+ embedding_model_name=$(azd env get-value AZURE_OPENAI_EMBEDDING_MODEL)
fi
-if [ -z "$searchEndpoint" ]; then
- searchEndpoint=$(azd env get-value AZURE_AI_SEARCH_ENDPOINT)
+if [ -z "$aiFoundryResourceId" ]; then
+ aiFoundryResourceId=$(azd env get-value AI_FOUNDRY_RESOURCE_ID)
fi
-# Function to enable public network access temporarily
-enable_public_access(){
- echo "=== Temporarily enabling public network access for services ==="
- # Enable public access for AI Foundry
- # Extract the account resource ID (remove /projects/... part if present)
- aif_account_resource_id=$(echo "$aiFoundryResourceId" | sed 's|/projects/.*||')
- aif_resource_name=$(basename "$aif_account_resource_id")
- # Extract resource group from the AI Foundry account resource ID
- aif_resource_group=$(echo "$aif_account_resource_id" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
- # Extract subscription ID from the AI Foundry account resource ID
- aif_subscription_id=$(echo "$aif_account_resource_id" | sed -n 's|.*/subscriptions/\([^/]*\)/.*|\1|p')
-
- original_foundry_public_access=$(az cognitiveservices account show \
- --name "$aif_resource_name" \
- --resource-group "$aif_resource_group" \
- --subscription "$aif_subscription_id" \
- --query "properties.publicNetworkAccess" \
- --output tsv)
- if [ -z "$original_foundry_public_access" ] || [ "$original_foundry_public_access" = "null" ]; then
- echo "โ Info: Could not retrieve AI Foundry network access status."
- echo " AI Foundry network access might be managed differently."
- elif [ "$original_foundry_public_access" != "Enabled" ]; then
- echo "Current AI Foundry public access: $original_foundry_public_access"
- echo "Enabling public access for AI Foundry resource: $aif_resource_name (Resource Group: $aif_resource_group)"
- if MSYS_NO_PATHCONV=1 az resource update \
- --ids "$aif_account_resource_id" \
- --api-version 2024-10-01 \
- --set properties.publicNetworkAccess=Enabled properties.apiProperties="{}" \
- --output none; then
- echo "โ AI Foundry public access enabled"
- else
- echo "โ Warning: Failed to enable AI Foundry public access automatically."
- fi
- else
- echo "โ AI Foundry public access already enabled - no changes needed"
- fi
-
- # Wait for changes to take effect - Azure network changes can take 30-60 seconds to propagate
- echo "Waiting for network access changes to propagate (this may take up to 60 seconds)..."
- sleep 30
-
- # Verify that public access is actually enabled by checking the current state
- echo "Verifying public network access is enabled..."
- current_access=$(az cognitiveservices account show \
- --name "$aif_resource_name" \
- --resource-group "$aif_resource_group" \
- --subscription "$aif_subscription_id" \
- --query "properties.publicNetworkAccess" \
- --output tsv 2>/dev/null || echo "Unknown")
-
- if [ "$current_access" = "Enabled" ]; then
- echo "โ Verified: Public network access is enabled"
- else
- echo "โ Warning: Public access verification returned: $current_access"
- echo " Waiting additional 30 seconds for propagation..."
- sleep 30
- fi
-
- echo "=== Public network access enabled successfully ==="
- return 0
-}
-
-# Function to restore original network access settings
-restore_network_access() {
- echo "=== Restoring original network access settings ==="
-
- # Restore AI Foundry access only if it was changed from the original state
- if [ -n "$original_foundry_public_access" ] && [ "$original_foundry_public_access" != "Enabled" ]; then
- echo "Restoring AI Foundry public access to: $original_foundry_public_access"
- # Reconstruct the AI Foundry resource ID for restoration
- aif_account_resource_id=$(echo "$aiFoundryResourceId" | sed 's|/projects/.*||')
- # Try using the working approach to restore the original setting
- if MSYS_NO_PATHCONV=1 az resource update \
- --ids "$aif_account_resource_id" \
- --api-version 2024-10-01 \
- --set properties.publicNetworkAccess="$original_foundry_public_access" \
- --set properties.apiProperties.qnaAzureSearchEndpointKey="" \
- --set properties.networkAcls.bypass="AzureServices" \
- --output none 2>/dev/null; then
- echo "โ AI Foundry access restored"
- else
- echo "โ Warning: Failed to restore AI Foundry access automatically."
- echo " Please manually restore network access in the Azure portal if needed."
- fi
- else
- echo "AI Foundry access unchanged (no restoration needed)"
- fi
-
- echo "=== Network access restoration completed ==="
-}
-
-# Function to handle script cleanup on exit
-cleanup_on_exit() {
- exit_code=$?
- echo ""
- if [ $exit_code -ne 0 ]; then
- echo "Script failed with exit code: $exit_code"
- fi
- echo "Performing cleanup..."
- restore_network_access
- exit $exit_code
-}
-
-# Set up trap to ensure cleanup happens on exit
-trap cleanup_on_exit EXIT INT TERM
-
-# Check if all required arguments are provided
-if [ -z "$projectEndpoint" ] || [ -z "$solutionName" ] || [ -z "$gptModelName" ] || [ -z "$aiFoundryResourceId" ] || [ -z "$apiAppName" ] || [ -z "$resourceGroup" ] || [ -z "$searchEndpoint" ]; then
- echo "Usage: $0 "
- exit 1
+if [ -z "$aiSearchResourceId" ]; then
+ aiSearchResourceId=$(azd env get-value AI_SEARCH_SERVICE_RESOURCE_ID)
fi
+if [ -z "$cosmosdb_account" ]; then
+ cosmosdb_account=$(azd env get-value AZURE_COSMOSDB_ACCOUNT)
+fi
+
+
# Check if user is logged in to Azure
echo "Checking Azure authentication..."
if az account show &> /dev/null; then
echo "Already authenticated with Azure."
else
# Use Azure CLI login if running locally
- # echo "Authenticating with Azure CLI..."
+ echo "Authenticating with Azure CLI..."
az login
fi
echo "Getting principal id (user or service principal)"
-# Temporarily disable exit on error for principal detection
-set +e
# Try to get signed-in user first (for interactive logins)
signed_user_id=$(az ad signed-in-user show --query id -o tsv 2>/dev/null)
@@ -195,66 +104,183 @@ if [ -z "$signed_user_id" ]; then
else
echo "Logged in as user: $signed_user_id"
fi
-# Re-enable exit on error
-set -e
-echo "Checking if the principal has Azure AI User role on the AI Foundry"
+echo "Checking if the principal has Search roles on the AI Search Service"
+# search service contributor role id: 7ca78c08-252a-4471-8644-bb5ff32d4ba0
+# search index data contributor role id: 8ebe5a00-799e-43f5-93ac-243d3dce84a7
+# search index data reader role id: 1407120a-92aa-4202-b7e9-c0e197c71c8f
if [ "$SKIP_ROLE_ASSIGNMENT" != "true" ] && [ -n "$signed_user_id" ]; then
role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list \
- --role "53ca6127-db72-4b80-b1b0-d745d6d5456d" \
- --scope "$aiFoundryResourceId" \
+ --role "7ca78c08-252a-4471-8644-bb5ff32d4ba0" \
+ --scope "$aiSearchResourceId" \
--assignee "$signed_user_id" \
--query "[].roleDefinitionId" -o tsv)
if [ -z "$role_assignment" ]; then
- echo "Principal does not have the Azure AI User role. Assigning the role..."
+ echo "Principal does not have the search service contributor role. Assigning the role..."
MSYS_NO_PATHCONV=1 az role assignment create \
--assignee "$signed_user_id" \
- --role "53ca6127-db72-4b80-b1b0-d745d6d5456d" \
- --scope "$aiFoundryResourceId" \
+ --role "7ca78c08-252a-4471-8644-bb5ff32d4ba0" \
+ --scope "$aiSearchResourceId" \
--output none
if [ $? -eq 0 ]; then
- echo "Azure AI User role assigned successfully."
+ echo "Search service contributor role assigned successfully."
else
- echo "Failed to assign Azure AI User role."
+ echo "Failed to assign search service contributor role."
exit 1
fi
else
- echo "Principal already has the Azure AI User role."
+ echo "Principal already has the search service contributor role."
+ fi
+
+ role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list \
+ --role "8ebe5a00-799e-43f5-93ac-243d3dce84a7" \
+ --scope "$aiSearchResourceId" \
+ --assignee "$signed_user_id" \
+ --query "[].roleDefinitionId" -o tsv)
+
+ if [ -z "$role_assignment" ]; then
+ echo "Principal does not have the search index data contributor role. Assigning the role..."
+ MSYS_NO_PATHCONV=1 az role assignment create \
+ --assignee "$signed_user_id" \
+ --role "8ebe5a00-799e-43f5-93ac-243d3dce84a7" \
+ --scope "$aiSearchResourceId" \
+ --output none
+
+ if [ $? -eq 0 ]; then
+ echo "Search index data contributor role assigned successfully."
+ else
+ echo "Failed to assign search index data contributor role."
+ exit 1
+ fi
+ else
+ echo "Principal already has the search index data contributor role."
fi
else
echo "Skipping role assignment (will rely on existing permissions)"
fi
+if [ "$SKIP_ROLE_ASSIGNMENT" != "true" ] && [ -n "$signed_user_id" ]; then
+ role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list \
+ --role "1407120a-92aa-4202-b7e9-c0e197c71c8f" \
+ --scope "$aiSearchResourceId" \
+ --assignee "$signed_user_id" \
+ --query "[].roleDefinitionId" -o tsv)
-requirementFile="infra/scripts/agent_scripts/requirements.txt"
+ if [ -z "$role_assignment" ]; then
+ echo "Principal does not have the search index data reader role. Assigning the role..."
+ MSYS_NO_PATHCONV=1 az role assignment create \
+ --assignee "$signed_user_id" \
+ --role "1407120a-92aa-4202-b7e9-c0e197c71c8f" \
+ --scope "$aiSearchResourceId" \
+ --output none
-# Download and install Python requirements
-python -m pip install --upgrade pip
-python -m pip install --quiet -r "$requirementFile"
+ if [ $? -eq 0 ]; then
+ echo "Search index data reader role assigned successfully."
+ else
+ echo "Failed to assign search index data reader role."
+ exit 1
+ fi
+ else
+ echo "Principal already has the search index data reader role."
+ fi
+fi
+
+# Check if the principal has the Cosmos DB Built-in Data Contributor role
+if [ "$SKIP_ROLE_ASSIGNMENT" != "true" ] && [ -n "$signed_user_id" ]; then
+ echo "Checking if principal has the Cosmos DB Built-in Data Contributor role"
+ roleExists=$(az cosmosdb sql role assignment list \
+ --resource-group $resource_group \
+ --account-name $cosmosdb_account \
+ --query "[?roleDefinitionId.ends_with(@, '00000000-0000-0000-0000-000000000002') && principalId == '$signed_user_id']" -o tsv)
-# Enable public network access for required services
-enable_public_access
-if [ $? -ne 0 ]; then
- echo "Error: Failed to enable public network access for services."
- exit 1
+ # Check if the role exists
+ if [ -n "$roleExists" ]; then
+ echo "Principal already has the Cosmos DB Built-in Data Contributor role."
+ else
+ echo "Principal does not have the Cosmos DB Built-in Data Contributor role. Assigning the role."
+ MSYS_NO_PATHCONV=1 az cosmosdb sql role assignment create \
+ --resource-group $resource_group \
+ --account-name $cosmosdb_account \
+ --role-definition-id 00000000-0000-0000-0000-000000000002 \
+ --principal-id $signed_user_id \
+ --scope "/" \
+ --output none
+ if [ $? -eq 0 ]; then
+ echo "Cosmos DB Built-in Data Contributor role assigned successfully."
+ else
+ echo "Failed to assign Cosmos DB Built-in Data Contributor role."
+ fi
+ fi
+else
+ echo "Skipping Cosmos DB role assignment (will rely on existing permissions)"
fi
-# Execute the Python scripts
-echo "Running Python agents creation script..."
-python_output=$(python infra/scripts/agent_scripts/01_create_agents.py --ai_project_endpoint="$projectEndpoint" --solution_name="$solutionName" --gpt_model_name="$gptModelName" --ai_search_endpoint="$searchEndpoint")
-eval $(echo "$python_output" | grep -E "^(chatAgentId|productAgentId|policyAgentId)=")
+# role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list \
+# --role "00000000-0000-0000-0000-000000000002" \
+# --scope "$cosmosdbAccountId" \
+# --assignee "$signed_user_id" \
+# --query "[].roleDefinitionId" -o tsv)
+
+# if [ -z "$role_assignment" ]; then
+# echo "User does not have the Cosmos DB account contributor role. Assigning the role..."
+# MSYS_NO_PATHCONV=1 az role assignment create \
+# --assignee "$signed_user_id" \
+# --role "00000000-0000-0000-0000-000000000002" \
+# --scope "$cosmosdbAccountId" \
+# --output none
+
+# if [ $? -eq 0 ]; then
+# echo "Cosmos DB account contributor role assigned successfully."
+# else
+# echo "Failed to assign Cosmos DB account contributor role."
+# exit 1
+# fi
+# else
+# echo "User already has the Cosmos DB account contributor role."
+# fi
+
+# role_assignment=$(MSYS_NO_PATHCONV=1 az cosmosdb sql role assignment list \
+# --account-name "$cosmosdb_account" \
+# --resource-group "$resource_group" \
+# --scope "$cosmosdbAccountId" \
+# --principal-id "$signed_user_id" \
+# --query "[].roleDefinitionId" -o tsv)
-echo "Agents creation completed."
+# if [ -z "$role_assignment" ]; then
+# echo "User does not have the Cosmos DB SQL role. Assigning the role..."
+# MSYS_NO_PATHCONV=1 az cosmosdb sql role assignment create \
+# --account-name "$cosmosdb_account" \
+# --resource-group "$resource_group" \
+# --principal-id "$signed_user_id" \
+# --role-definition-id "00000000-0000-0000-0000-000000000002" \
+# --scope "$cosmosdbAccountId" \
+# --output none
+
+# if [ $? -eq 0 ]; then
+# echo "Cosmos DB SQL role assigned successfully."
+# else
+# echo "Failed to assign Cosmos DB SQL role."
+# exit 1
+# fi
+# else
+# echo "User already has the Cosmos DB SQL role."
+# fi
+
+# python -m venv .venv
+
+# .venv\Scripts\activate
+
+requirementFile="infra/scripts/data_scripts/requirements.txt"
+
+# Download and install Python requirements
+python -m pip install --upgrade pip
+python -m pip install --quiet -r "$requirementFile"
-# Update environment variables of API App
-az webapp config appsettings set \
- --resource-group "$resourceGroup" \
- --name "$apiAppName" \
- --settings FOUNDRY_CHAT_AGENT_ID="$chatAgentId" FOUNDRY_CUSTOM_PRODUCT_AGENT_ID="$productAgentId" FOUNDRY_POLICY_AGENT_ID="$policyAgentId" \
- -o none
-echo "Environment variables updated for App Service: $apiAppName"
-echo "Network access will be restored to original settings..."
\ No newline at end of file
+# python pip install -r infra/scripts/data_scripts/requirements.txt --quiet
+python infra/scripts/data_scripts/01_create_products_search_index.py --ai_search_endpoint="$ai_search_endpoint" --azure_openai_endpoint="$azure_openai_endpoint" --embedding_model_name="$embedding_model_name"
+python infra/scripts/data_scripts/02_create_policies_search_index.py --ai_search_endpoint="$ai_search_endpoint" --azure_openai_endpoint="$azure_openai_endpoint" --embedding_model_name="$embedding_model_name"
+python infra/scripts/data_scripts/03_write_products_to_cosmos.py --cosmosdb_account="$cosmosdb_account"
\ No newline at end of file
From b6f78a9cf56db2715ae47b9a4ae4cb43ada004dd Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Wed, 28 Jan 2026 20:47:15 +0530
Subject: [PATCH 24/38] fix: Enhance Python command detection to ensure it
executes correctly and provide installation guidance
---
.../run_create_agents_scripts.sh | 23 +++++++++++++------
1 file changed, 16 insertions(+), 7 deletions(-)
diff --git a/infra/scripts/agent_scripts/run_create_agents_scripts.sh b/infra/scripts/agent_scripts/run_create_agents_scripts.sh
index d5092d29..ed609f6a 100644
--- a/infra/scripts/agent_scripts/run_create_agents_scripts.sh
+++ b/infra/scripts/agent_scripts/run_create_agents_scripts.sh
@@ -243,16 +243,25 @@ fi
requirementFile="infra/scripts/agent_scripts/requirements.txt"
-# Determine python command (python3 preferred, fallback to python)
-if command -v python3 &> /dev/null; then
- PYTHON_CMD="python3"
-elif command -v python &> /dev/null; then
- PYTHON_CMD="python"
-else
+# Determine python command - verify it actually works (not just exists)
+# On Windows, 'python3' may exist as a Microsoft Store alias that doesn't work
+PYTHON_CMD=""
+for cmd in python3 python; do
+ if command -v "$cmd" &> /dev/null; then
+ # Verify the command actually executes and returns a version
+ if "$cmd" --version &> /dev/null; then
+ PYTHON_CMD="$cmd"
+ break
+ fi
+ fi
+done
+
+if [ -z "$PYTHON_CMD" ]; then
echo "Error: Python is not installed or not in PATH"
+ echo "Please install Python 3.9+ from https://www.python.org/downloads/"
exit 1
fi
-echo "Using Python command: $PYTHON_CMD"
+echo "Using Python command: $PYTHON_CMD ($($PYTHON_CMD --version))"
# Download and install Python requirements
$PYTHON_CMD -m pip install --upgrade pip
From 49f9cde225ec82a87ff3f57794df7c8dd6845ff7 Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Fri, 30 Jan 2026 11:25:29 +0530
Subject: [PATCH 25/38] feat: Add run_e2e_tests input option to deployment
workflows for enhanced testing flexibility
---
.github/workflows/deploy-linux.yml | 42 ++++++++++-----------
.github/workflows/deploy-orchestrator.yml | 42 ++++++++++-----------
.github/workflows/deploy-windows.yml | 42 ++++++++++-----------
.github/workflows/job-deploy.yml | 10 ++---
.github/workflows/job-send-notification.yml | 21 ++++++-----
5 files changed, 78 insertions(+), 79 deletions(-)
diff --git a/.github/workflows/deploy-linux.yml b/.github/workflows/deploy-linux.yml
index 563919f9..3674f1c7 100644
--- a/.github/workflows/deploy-linux.yml
+++ b/.github/workflows/deploy-linux.yml
@@ -52,15 +52,15 @@ on:
required: false
default: false
type: boolean
- # run_e2e_tests:
- # description: 'Run End-to-End Tests'
- # required: false
- # default: 'GoldenPath-Testing'
- # type: choice
- # options:
- # - 'GoldenPath-Testing'
- # - 'Smoke-Testing'
- # - 'None'
+ run_e2e_tests:
+ description: 'Run End-to-End Tests'
+ required: false
+ default: 'GoldenPath-Testing'
+ type: choice
+ options:
+ - 'GoldenPath-Testing'
+ - 'Smoke-Testing'
+ - 'None'
AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID:
description: 'Log Analytics Workspace ID (Optional)'
required: false
@@ -91,7 +91,7 @@ jobs:
exp: ${{ steps.validate.outputs.exp }}
build_docker_image: ${{ steps.validate.outputs.build_docker_image }}
cleanup_resources: ${{ steps.validate.outputs.cleanup_resources }}
- # run_e2e_tests: ${{ steps.validate.outputs.run_e2e_tests }}
+ run_e2e_tests: ${{ steps.validate.outputs.run_e2e_tests }}
azure_env_log_analytics_workspace_id: ${{ steps.validate.outputs.azure_env_log_analytics_workspace_id }}
azure_existing_ai_project_resource_id: ${{ steps.validate.outputs.azure_existing_ai_project_resource_id }}
existing_webapp_url: ${{ steps.validate.outputs.existing_webapp_url }}
@@ -106,7 +106,7 @@ jobs:
INPUT_EXP: ${{ github.event.inputs.EXP }}
INPUT_BUILD_DOCKER_IMAGE: ${{ github.event.inputs.build_docker_image }}
INPUT_CLEANUP_RESOURCES: ${{ github.event.inputs.cleanup_resources }}
- # INPUT_RUN_E2E_TESTS: ${{ github.event.inputs.run_e2e_tests }}
+ INPUT_RUN_E2E_TESTS: ${{ github.event.inputs.run_e2e_tests }}
INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ github.event.inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}
INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ github.event.inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
INPUT_EXISTING_WEBAPP_URL: ${{ github.event.inputs.existing_webapp_url }}
@@ -175,14 +175,14 @@ jobs:
echo "โ
cleanup_resources: '$CLEANUP_RESOURCES' is valid"
fi
- # # Validate run_e2e_tests (specific allowed values)
- # TEST_OPTION="${INPUT_RUN_E2E_TESTS:-GoldenPath-Testing}"
- # if [[ "$TEST_OPTION" != "GoldenPath-Testing" && "$TEST_OPTION" != "Smoke-Testing" && "$TEST_OPTION" != "None" ]]; then
- # echo "โ ERROR: run_e2e_tests must be one of: GoldenPath-Testing, Smoke-Testing, None, got: '$TEST_OPTION'"
- # VALIDATION_FAILED=true
- # else
- # echo "โ
run_e2e_tests: '$TEST_OPTION' is valid"
- # fi
+ # Validate run_e2e_tests (specific allowed values)
+ TEST_OPTION="${INPUT_RUN_E2E_TESTS:-GoldenPath-Testing}"
+ if [[ "$TEST_OPTION" != "GoldenPath-Testing" && "$TEST_OPTION" != "Smoke-Testing" && "$TEST_OPTION" != "None" ]]; then
+ echo "โ ERROR: run_e2e_tests must be one of: GoldenPath-Testing, Smoke-Testing, None, got: '$TEST_OPTION'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
run_e2e_tests: '$TEST_OPTION' is valid"
+ fi
# Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (optional, Azure Resource ID format)
if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then
@@ -242,7 +242,7 @@ jobs:
echo "exp=$EXP_ENABLED" >> $GITHUB_OUTPUT
echo "build_docker_image=$BUILD_DOCKER" >> $GITHUB_OUTPUT
echo "cleanup_resources=$CLEANUP_RESOURCES" >> $GITHUB_OUTPUT
- # echo "run_e2e_tests=$TEST_OPTION" >> $GITHUB_OUTPUT
+ echo "run_e2e_tests=$TEST_OPTION" >> $GITHUB_OUTPUT
echo "azure_env_log_analytics_workspace_id=$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" >> $GITHUB_OUTPUT
echo "azure_existing_ai_project_resource_id=$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" >> $GITHUB_OUTPUT
echo "existing_webapp_url=$INPUT_EXISTING_WEBAPP_URL" >> $GITHUB_OUTPUT
@@ -259,7 +259,7 @@ jobs:
EXP: ${{ needs.validate-inputs.outputs.exp == 'true' }}
build_docker_image: ${{ needs.validate-inputs.outputs.build_docker_image == 'true' }}
cleanup_resources: ${{ needs.validate-inputs.outputs.cleanup_resources == 'true' }}
- # run_e2e_tests: ${{ needs.validate-inputs.outputs.run_e2e_tests }}
+ run_e2e_tests: ${{ needs.validate-inputs.outputs.run_e2e_tests }}
AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ needs.validate-inputs.outputs.azure_env_log_analytics_workspace_id }}
AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ needs.validate-inputs.outputs.azure_existing_ai_project_resource_id }}
existing_webapp_url: ${{ needs.validate-inputs.outputs.existing_webapp_url }}
diff --git a/.github/workflows/deploy-orchestrator.yml b/.github/workflows/deploy-orchestrator.yml
index 92a8a6e6..fea49ef8 100644
--- a/.github/workflows/deploy-orchestrator.yml
+++ b/.github/workflows/deploy-orchestrator.yml
@@ -41,11 +41,11 @@ on:
required: false
default: false
type: boolean
- # run_e2e_tests:
- # description: 'Run End-to-End Tests'
- # required: false
- # default: 'GoldenPath-Testing'
- # type: string
+ run_e2e_tests:
+ description: 'Run End-to-End Tests'
+ required: false
+ default: 'GoldenPath-Testing'
+ type: string
AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID:
description: 'Log Analytics Workspace ID (Optional)'
required: false
@@ -93,44 +93,42 @@ jobs:
AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}
AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
docker_image_tag: ${{ needs.docker-build.outputs.IMAGE_TAG }}
- # run_e2e_tests: ${{ inputs.run_e2e_tests }}
+ run_e2e_tests: ${{ inputs.run_e2e_tests }}
cleanup_resources: ${{ inputs.cleanup_resources }}
secrets: inherit
- # e2e-test:
- # if: "!cancelled() && ((needs.deploy.result == 'success' && needs.deploy.outputs.CONTAINER_WEB_APPURL != '') || (inputs.existing_webapp_url != '' && inputs.existing_webapp_url != null)) && (inputs.trigger_type != 'workflow_dispatch' || (inputs.run_e2e_tests != 'None' && inputs.run_e2e_tests != '' && inputs.run_e2e_tests != null))"
- # needs: [docker-build, deploy]
- # uses: ./.github/workflows/test-automation-v2.yml
- # with:
- # TEST_URL: ${{ needs.deploy.outputs.CONTAINER_WEB_APPURL || inputs.existing_webapp_url }}
- # TEST_SUITE: ${{ inputs.trigger_type == 'workflow_dispatch' && inputs.run_e2e_tests || 'GoldenPath-Testing' }}
- # secrets: inherit
+ e2e-test:
+ if: "!cancelled() && ((needs.deploy.result == 'success' && needs.deploy.outputs.CONTAINER_WEB_APPURL != '') || (inputs.existing_webapp_url != '' && inputs.existing_webapp_url != null)) && (inputs.trigger_type != 'workflow_dispatch' || (inputs.run_e2e_tests != 'None' && inputs.run_e2e_tests != '' && inputs.run_e2e_tests != null))"
+ needs: [docker-build, deploy]
+ uses: ./.github/workflows/test-automation-v2.yml
+ with:
+ TEST_URL: ${{ needs.deploy.outputs.CONTAINER_WEB_APPURL || inputs.existing_webapp_url }}
+ TEST_SUITE: ${{ inputs.trigger_type == 'workflow_dispatch' && inputs.run_e2e_tests || 'GoldenPath-Testing' }}
+ secrets: inherit
send-notification:
if: "!cancelled()"
- needs: [docker-build, deploy]
- # needs: [docker-build, deploy, e2e-test]
+ needs: [docker-build, deploy, e2e-test]
uses: ./.github/workflows/job-send-notification.yml
with:
trigger_type: ${{ inputs.trigger_type }}
waf_enabled: ${{ inputs.waf_enabled }}
EXP: ${{ inputs.EXP }}
- # run_e2e_tests: ${{ inputs.run_e2e_tests }}
+ run_e2e_tests: ${{ inputs.run_e2e_tests }}
existing_webapp_url: ${{ inputs.existing_webapp_url }}
deploy_result: ${{ needs.deploy.result }}
- # e2e_test_result: ${{ needs.e2e-test.result }}
+ e2e_test_result: ${{ needs.e2e-test.result }}
CONTAINER_WEB_APPURL: ${{ needs.deploy.outputs.CONTAINER_WEB_APPURL }}
RESOURCE_GROUP_NAME: ${{ needs.deploy.outputs.RESOURCE_GROUP_NAME }}
QUOTA_FAILED: ${{ needs.deploy.outputs.QUOTA_FAILED }}
- # TEST_SUCCESS: ${{ needs.e2e-test.outputs.TEST_SUCCESS }}
- # TEST_REPORT_URL: ${{ needs.e2e-test.outputs.TEST_REPORT_URL }}
+ TEST_SUCCESS: ${{ needs.e2e-test.outputs.TEST_SUCCESS }}
+ TEST_REPORT_URL: ${{ needs.e2e-test.outputs.TEST_REPORT_URL }}
secrets: inherit
cleanup-deployment:
if: "always() && needs.deploy.outputs.RESOURCE_GROUP_NAME != '' && inputs.existing_webapp_url == '' && (inputs.trigger_type != 'workflow_dispatch' || inputs.cleanup_resources)"
- needs: [docker-build, deploy]
- # needs: [docker-build, deploy, e2e-test]
+ needs: [docker-build, deploy, e2e-test]
uses: ./.github/workflows/job-cleanup-deployment.yml
with:
diff --git a/.github/workflows/deploy-windows.yml b/.github/workflows/deploy-windows.yml
index b21e6d8b..2987af50 100644
--- a/.github/workflows/deploy-windows.yml
+++ b/.github/workflows/deploy-windows.yml
@@ -47,15 +47,15 @@ on:
default: false
type: boolean
- # run_e2e_tests:
- # description: 'Run End-to-End Tests'
- # required: false
- # default: 'GoldenPath-Testing'
- # type: choice
- # options:
- # - 'GoldenPath-Testing'
- # - 'Smoke-Testing'
- # - 'None'
+ run_e2e_tests:
+ description: 'Run End-to-End Tests'
+ required: false
+ default: 'GoldenPath-Testing'
+ type: choice
+ options:
+ - 'GoldenPath-Testing'
+ - 'Smoke-Testing'
+ - 'None'
AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID:
description: 'Log Analytics Workspace ID (Optional)'
@@ -87,7 +87,7 @@ jobs:
exp: ${{ steps.validate.outputs.exp }}
build_docker_image: ${{ steps.validate.outputs.build_docker_image }}
cleanup_resources: ${{ steps.validate.outputs.cleanup_resources }}
- # run_e2e_tests: ${{ steps.validate.outputs.run_e2e_tests }}
+ run_e2e_tests: ${{ steps.validate.outputs.run_e2e_tests }}
azure_env_log_analytics_workspace_id: ${{ steps.validate.outputs.azure_env_log_analytics_workspace_id }}
azure_existing_ai_project_resource_id: ${{ steps.validate.outputs.azure_existing_ai_project_resource_id }}
existing_webapp_url: ${{ steps.validate.outputs.existing_webapp_url }}
@@ -102,7 +102,7 @@ jobs:
INPUT_EXP: ${{ github.event.inputs.EXP }}
INPUT_BUILD_DOCKER_IMAGE: ${{ github.event.inputs.build_docker_image }}
INPUT_CLEANUP_RESOURCES: ${{ github.event.inputs.cleanup_resources }}
- # INPUT_RUN_E2E_TESTS: ${{ github.event.inputs.run_e2e_tests }}
+ INPUT_RUN_E2E_TESTS: ${{ github.event.inputs.run_e2e_tests }}
INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ github.event.inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}
INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ github.event.inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
INPUT_EXISTING_WEBAPP_URL: ${{ github.event.inputs.existing_webapp_url }}
@@ -171,14 +171,14 @@ jobs:
echo "โ
cleanup_resources: '$CLEANUP_RESOURCES' is valid"
fi
- # # Validate run_e2e_tests (specific allowed values)
- # TEST_OPTION="${INPUT_RUN_E2E_TESTS:-GoldenPath-Testing}"
- # if [[ "$TEST_OPTION" != "GoldenPath-Testing" && "$TEST_OPTION" != "Smoke-Testing" && "$TEST_OPTION" != "None" ]]; then
- # echo "โ ERROR: run_e2e_tests must be one of: GoldenPath-Testing, Smoke-Testing, None, got: '$TEST_OPTION'"
- # VALIDATION_FAILED=true
- # else
- # echo "โ
run_e2e_tests: '$TEST_OPTION' is valid"
- # fi
+ # Validate run_e2e_tests (specific allowed values)
+ TEST_OPTION="${INPUT_RUN_E2E_TESTS:-GoldenPath-Testing}"
+ if [[ "$TEST_OPTION" != "GoldenPath-Testing" && "$TEST_OPTION" != "Smoke-Testing" && "$TEST_OPTION" != "None" ]]; then
+ echo "โ ERROR: run_e2e_tests must be one of: GoldenPath-Testing, Smoke-Testing, None, got: '$TEST_OPTION'"
+ VALIDATION_FAILED=true
+ else
+ echo "โ
run_e2e_tests: '$TEST_OPTION' is valid"
+ fi
# Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (optional, Azure Resource ID format)
if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then
@@ -238,7 +238,7 @@ jobs:
echo "exp=$EXP_ENABLED" >> $GITHUB_OUTPUT
echo "build_docker_image=$BUILD_DOCKER" >> $GITHUB_OUTPUT
echo "cleanup_resources=$CLEANUP_RESOURCES" >> $GITHUB_OUTPUT
- # echo "run_e2e_tests=$TEST_OPTION" >> $GITHUB_OUTPUT
+ echo "run_e2e_tests=$TEST_OPTION" >> $GITHUB_OUTPUT
echo "azure_env_log_analytics_workspace_id=$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" >> $GITHUB_OUTPUT
echo "azure_existing_ai_project_resource_id=$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" >> $GITHUB_OUTPUT
echo "existing_webapp_url=$INPUT_EXISTING_WEBAPP_URL" >> $GITHUB_OUTPUT
@@ -255,7 +255,7 @@ jobs:
EXP: ${{ needs.validate-inputs.outputs.exp == 'true' }}
build_docker_image: ${{ needs.validate-inputs.outputs.build_docker_image == 'true' }}
cleanup_resources: ${{ needs.validate-inputs.outputs.cleanup_resources == 'true' }}
- # run_e2e_tests: ${{ needs.validate-inputs.outputs.run_e2e_tests }}
+ run_e2e_tests: ${{ needs.validate-inputs.outputs.run_e2e_tests }}
AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ needs.validate-inputs.outputs.azure_env_log_analytics_workspace_id }}
AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ needs.validate-inputs.outputs.azure_existing_ai_project_resource_id }}
existing_webapp_url: ${{ needs.validate-inputs.outputs.existing_webapp_url }}
diff --git a/.github/workflows/job-deploy.yml b/.github/workflows/job-deploy.yml
index be726813..8d661b08 100644
--- a/.github/workflows/job-deploy.yml
+++ b/.github/workflows/job-deploy.yml
@@ -44,11 +44,11 @@ on:
required: false
default: false
type: boolean
- # run_e2e_tests:
- # description: 'Run End-to-End Tests'
- # required: false
- # default: 'GoldenPath-Testing'
- # type: string
+ run_e2e_tests:
+ description: 'Run End-to-End Tests'
+ required: false
+ default: 'GoldenPath-Testing'
+ type: string
existing_webapp_url:
description: 'Existing Container WebApp URL (Skips Deployment)'
required: false
diff --git a/.github/workflows/job-send-notification.yml b/.github/workflows/job-send-notification.yml
index 48f2bdf4..5821f970 100644
--- a/.github/workflows/job-send-notification.yml
+++ b/.github/workflows/job-send-notification.yml
@@ -19,11 +19,11 @@ on:
required: false
default: false
type: boolean
- # run_e2e_tests:
- # description: 'Run End-to-End Tests'
- # required: false
- # default: 'GoldenPath-Testing'
- # type: string
+ run_e2e_tests:
+ description: 'Run End-to-End Tests'
+ required: false
+ default: 'GoldenPath-Testing'
+ type: string
existing_webapp_url:
description: 'Existing Container WebApp URL (Skips Deployment)'
required: false
@@ -33,10 +33,11 @@ on:
description: 'Deploy job result (success, failure, skipped)'
required: true
type: string
- # e2e_test_result:
- # description: 'E2E test job result (success, failure, skipped)'
- # required: true
- # type: string
+ e2e_test_result:
+ description: 'E2E test job result (success, failure, skipped)'
+ required: false
+ default: ''
+ type: string
CONTAINER_WEB_APPURL:
description: 'Container Web App URL'
required: false
@@ -68,7 +69,7 @@ env:
BRANCH_NAME: ${{ github.event.workflow_run.head_branch || github.head_ref || github.ref_name }}
WAF_ENABLED: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.waf_enabled || false) || false }}
EXP: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.EXP || false) || false }}
- # RUN_E2E_TESTS: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.run_e2e_tests || 'GoldenPath-Testing') || 'GoldenPath-Testing' }}
+ RUN_E2E_TESTS: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.run_e2e_tests || 'GoldenPath-Testing') || 'GoldenPath-Testing' }}
jobs:
send-notification:
From 1e637ac7c8c1c7fba53e5d56af47ec46865047aa Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Fri, 30 Jan 2026 15:03:41 +0530
Subject: [PATCH 26/38] feat: Add E2E_TEST input option and wait step for app
readiness in deployment workflows
---
.github/workflows/job-deploy-linux.yml | 12 ++++++++++++
.github/workflows/job-deploy-windows.yml | 12 ++++++++++++
2 files changed, 24 insertions(+)
diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml
index e354140d..88a5866f 100644
--- a/.github/workflows/job-deploy-linux.yml
+++ b/.github/workflows/job-deploy-linux.yml
@@ -37,6 +37,10 @@ on:
AZURE_EXISTING_AI_PROJECT_RESOURCE_ID:
required: false
type: string
+ E2E_TEST:
+ required: false
+ type: string
+ default: 'false'
outputs:
CONTAINER_WEB_APPURL:
description: "Container Web App URL"
@@ -436,6 +440,14 @@ jobs:
sleep 300
echo "โ
Wait completed, data should now be available"
+ - name: Wait for App Readiness (E2E Tests)
+ if: inputs.E2E_TEST == 'true'
+ shell: bash
+ run: |
+ echo "โณ E2E tests enabled - Waiting 10 minutes for app to be fully ready..."
+ sleep 600
+ echo "โ
App readiness wait completed"
+
- name: Restore Private Network Access (WAF Only)
if: always() && inputs.WAF_ENABLED == 'true'
shell: bash
diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml
index 9a56ac83..53f138df 100644
--- a/.github/workflows/job-deploy-windows.yml
+++ b/.github/workflows/job-deploy-windows.yml
@@ -38,6 +38,10 @@ on:
AZURE_EXISTING_AI_PROJECT_RESOURCE_ID:
required: false
type: string
+ E2E_TEST:
+ required: false
+ type: string
+ default: 'false'
outputs:
CONTAINER_WEB_APPURL:
description: "Container Web App URL"
@@ -434,6 +438,14 @@ jobs:
sleep 300
echo "โ
Wait completed, data should now be available"
+ - name: Wait for App Readiness (E2E Tests)
+ if: inputs.E2E_TEST == 'true'
+ shell: bash
+ run: |
+ echo "โณ E2E tests enabled - Waiting 10 minutes for app to be fully ready..."
+ sleep 600
+ echo "โ
App readiness wait completed"
+
- name: Restore Private Network Access (WAF Only)
if: always() && inputs.WAF_ENABLED == 'true'
shell: bash
From 9d7973e675eb711cfdae60db027ab191a0fe1ab7 Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Fri, 30 Jan 2026 15:45:25 +0530
Subject: [PATCH 27/38] feat: Update app readiness wait condition for E2E tests
in deployment workflows
---
.github/workflows/job-deploy-linux.yml | 4 ++--
.github/workflows/job-deploy-windows.yml | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml
index 88a5866f..3240e9bf 100644
--- a/.github/workflows/job-deploy-linux.yml
+++ b/.github/workflows/job-deploy-linux.yml
@@ -441,10 +441,10 @@ jobs:
echo "โ
Wait completed, data should now be available"
- name: Wait for App Readiness (E2E Tests)
- if: inputs.E2E_TEST == 'true'
+ if: inputs.E2E_TEST == 'true' || (inputs.WAF_ENABLED == 'false' && inputs.EXP == 'false')
shell: bash
run: |
- echo "โณ E2E tests enabled - Waiting 10 minutes for app to be fully ready..."
+ echo "โณ E2E tests or Golden Path deployment - Waiting 10 minutes for app to be fully ready..."
sleep 600
echo "โ
App readiness wait completed"
diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml
index 53f138df..b946910a 100644
--- a/.github/workflows/job-deploy-windows.yml
+++ b/.github/workflows/job-deploy-windows.yml
@@ -439,10 +439,10 @@ jobs:
echo "โ
Wait completed, data should now be available"
- name: Wait for App Readiness (E2E Tests)
- if: inputs.E2E_TEST == 'true'
+ if: inputs.E2E_TEST == 'true' || (inputs.WAF_ENABLED == 'false' && inputs.EXP == 'false')
shell: bash
run: |
- echo "โณ E2E tests enabled - Waiting 10 minutes for app to be fully ready..."
+ echo "โณ E2E tests or Golden Path deployment - Waiting 10 minutes for app to be fully ready..."
sleep 600
echo "โ
App readiness wait completed"
From a30b49abff6496ab3d99cdce5d9fab36876be015 Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Fri, 30 Jan 2026 16:28:29 +0530
Subject: [PATCH 28/38] feat: Enhance response verification in
ask_question_and_verify method and add get_latest_ai_response method
---
tests/e2e-test/pages/webUserPage.py | 47 +++++++++++++++++++++++++++--
1 file changed, 44 insertions(+), 3 deletions(-)
diff --git a/tests/e2e-test/pages/webUserPage.py b/tests/e2e-test/pages/webUserPage.py
index 8dfed6f9..a106f983 100644
--- a/tests/e2e-test/pages/webUserPage.py
+++ b/tests/e2e-test/pages/webUserPage.py
@@ -207,6 +207,10 @@ def verify_response_contains_keywords(self, response_text, keywords):
def ask_question_and_verify(self, question, expected_keywords):
"""Ask a question and verify the response contains expected content"""
+ # Count existing AI response containers BEFORE asking the question
+ ai_response_selector = 'div[class*="bg-muted"]'
+ initial_response_count = self.page.locator(ai_response_selector).count()
+
# Clear any existing input first
text_area = self.page.locator(self.TYPE_QUESTION_TEXT_AREA)
text_area.click()
@@ -227,15 +231,52 @@ def ask_question_and_verify(self, question, expected_keywords):
# Wait for response with longer timeout
self.wait_for_response(timeout=45000)
- # Wait extra time to ensure new response has arrived
+ # Wait for a NEW response to appear (response count should increase)
+ try:
+ self.page.wait_for_function(
+ f"""(expectedCount) => {{
+ const responses = document.querySelectorAll('div[class*="bg-muted"]');
+ return responses.length > expectedCount;
+ }}""",
+ arg=initial_response_count,
+ timeout=60000
+ )
+ except:
+ pass
+
+ # Wait extra time to ensure new response has fully loaded
self.page.wait_for_timeout(5000)
- # Get the response
- response = self.get_last_response()
+ # Get the LATEST response specifically (the last one in the list)
+ response = self.get_latest_ai_response()
# Verify response contains expected content
contains_keyword, found_keyword = self.verify_response_contains_keywords(response, expected_keywords)
return response, contains_keyword, found_keyword
+
+ def get_latest_ai_response(self):
+ """Get the text content of the LATEST/MOST RECENT AI response only"""
+ # Wait for any dynamic content to load
+ self.page.wait_for_timeout(2000)
+
+ # Try to get the last AI response container
+ ai_response_selector = 'div[class*="bg-muted"]'
+ response_elements = self.page.locator(ai_response_selector)
+ response_count = response_elements.count()
+
+ if response_count > 0:
+ # Get the LAST response element (most recent)
+ last_response = response_elements.nth(response_count - 1)
+ response_text = last_response.text_content()
+
+ if response_text and len(response_text.strip()) > 20:
+ # Clean up the response text
+ import re
+ cleaned = re.sub(r'\s+', ' ', response_text).strip()
+ return cleaned
+
+ # Fallback to the original method if the above doesn't work
+ return self.get_last_response()
\ No newline at end of file
From 6c1611e93497d529763b52e6b74ac60805132a58 Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Fri, 30 Jan 2026 17:16:07 +0530
Subject: [PATCH 29/38] feat: Implement get_latest_ai_response method for
improved AI response retrieval and update test to utilize it
---
tests/e2e-test/pages/webUserPage.py | 100 +++++++++++++++++++++++-----
tests/e2e-test/tests/test_byocc.py | 4 +-
2 files changed, 87 insertions(+), 17 deletions(-)
diff --git a/tests/e2e-test/pages/webUserPage.py b/tests/e2e-test/pages/webUserPage.py
index a106f983..c992fb7f 100644
--- a/tests/e2e-test/pages/webUserPage.py
+++ b/tests/e2e-test/pages/webUserPage.py
@@ -257,26 +257,96 @@ def ask_question_and_verify(self, question, expected_keywords):
def get_latest_ai_response(self):
"""Get the text content of the LATEST/MOST RECENT AI response only"""
+ import re
+
# Wait for any dynamic content to load
- self.page.wait_for_timeout(2000)
+ self.page.wait_for_timeout(3000)
- # Try to get the last AI response container
- ai_response_selector = 'div[class*="bg-muted"]'
- response_elements = self.page.locator(ai_response_selector)
- response_count = response_elements.count()
+ # Method 1: Look for AI response containers within the chat panel
+ # These are typically marked with timestamps and contain the AI icon
+ chat_panel_selectors = [
+ # AI responses in chat panel - look for containers with AI indicator and timestamp
+ 'div[class*="bg-muted"]:has(svg):not(:has(img[alt*="Paint"]))',
+ # Messages within the chat scroll area
+ '[data-radix-scroll-area-viewport] div[class*="bg-muted"]',
+ # Direct chat message containers
+ 'div[class*="rounded-lg"][class*="bg-muted"]'
+ ]
- if response_count > 0:
- # Get the LAST response element (most recent)
- last_response = response_elements.nth(response_count - 1)
- response_text = last_response.text_content()
-
- if response_text and len(response_text.strip()) > 20:
- # Clean up the response text
- import re
- cleaned = re.sub(r'\s+', ' ', response_text).strip()
- return cleaned
+ for selector in chat_panel_selectors:
+ try:
+ response_elements = self.page.locator(selector)
+ response_count = response_elements.count()
+
+ if response_count > 0:
+ # Get the LAST response element (most recent)
+ last_response = response_elements.nth(response_count - 1)
+ response_text = last_response.text_content()
+
+ if response_text and len(response_text.strip()) > 20:
+ cleaned = re.sub(r'\s+', ' ', response_text).strip()
+ # Validate this looks like an AI text response, not a product card
+ if not self._is_product_card_text(cleaned):
+ return cleaned
+ except:
+ continue
+
+ # Method 2: Parse the full page and extract the latest AI response by timestamp
+ full_page_text = self.page.locator('body').text_content()
+
+ # Split by timestamps (e.g., "Jan 30, 10:49 AM")
+ timestamp_pattern = r'(Jan \d+, \d+:\d+ [AP]M)'
+ parts = re.split(timestamp_pattern, full_page_text)
+
+ # Find the last AI response (typically follows a timestamp and doesn't contain "You")
+ ai_responses = []
+ for i in range(len(parts) - 1, 0, -1):
+ part = parts[i].strip()
+ # Skip timestamps themselves
+ if re.match(timestamp_pattern, part):
+ continue
+ # Skip empty or very short parts
+ if len(part) < 30:
+ continue
+ # Skip user messages (typically short and followed by user indicator)
+ if 'You' in parts[i-1] if i > 0 else False:
+ continue
+ # Check if this looks like an AI response about the topic
+ if not self._is_product_card_text(part):
+ cleaned = re.sub(r'\s+', ' ', part).strip()
+ if len(cleaned) > 30:
+ ai_responses.append(cleaned)
+
+ if ai_responses:
+ return ai_responses[0] # Return the most recent non-product-card response
# Fallback to the original method if the above doesn't work
return self.get_last_response()
+
+ def _is_product_card_text(self, text):
+ """Check if text appears to be from a product card rather than an AI response"""
+ # Product cards typically contain repeated patterns of product names and prices
+ product_indicators = [
+ 'Blue Ash',
+ 'Cloud Drift',
+ 'Fog Harbor',
+ 'Glacier Tint',
+ 'Showing 16 results',
+ 'Products'
+ ]
+
+ # Count how many product indicators are present
+ indicator_count = sum(1 for indicator in product_indicators if indicator in text)
+
+ # If the text contains multiple product names in sequence, it's likely a product card list
+ if indicator_count >= 3:
+ return True
+
+ # Check for the pattern of multiple "59.50 USD" which indicates product listing
+ price_count = text.count('59.50 USD')
+ if price_count >= 2:
+ return True
+
+ return False
\ No newline at end of file
diff --git a/tests/e2e-test/tests/test_byocc.py b/tests/e2e-test/tests/test_byocc.py
index 3b69aef2..b500ab0f 100644
--- a/tests/e2e-test/tests/test_byocc.py
+++ b/tests/e2e-test/tests/test_byocc.py
@@ -142,10 +142,10 @@ def test_28907_golden_path_demo_script(self, page):
# Extra wait to ensure response is fully loaded
page.wait_for_timeout(8000)
- # Get response with multiple attempts if needed
+ # Get response with multiple attempts if needed - use get_latest_ai_response for better accuracy
response = ""
for attempt in range(3):
- response = web_user_page.get_last_response()
+ response = web_user_page.get_latest_ai_response()
if any(keyword.lower() in response.lower() for keyword in dissatisfaction_data["expected_responses"]):
break
logger.info(f"Attempt {attempt + 1}: Waiting longer for dissatisfaction response...")
From f6b41bb4f5a473a7edb2a10f66f5adc79fa87e55 Mon Sep 17 00:00:00 2001
From: Harmanpreet Kaur
Date: Wed, 4 Feb 2026 15:28:09 +0530
Subject: [PATCH 30/38] Temporarily enable public network access for Cosmos DB
and AI Foundry during data upload; restore original settings after
completion. Updated deployment workflow to comment out WAF-related steps for
clarity.
---
.github/workflows/job-deploy-linux.yml | 320 +++++++++---------
.github/workflows/job-deploy-windows.yml | 294 ++++++++--------
.../data_scripts/run_upload_data_scripts.sh | 205 ++++++++++-
3 files changed, 498 insertions(+), 321 deletions(-)
diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml
index 3240e9bf..66483f76 100644
--- a/.github/workflows/job-deploy-linux.yml
+++ b/.github/workflows/job-deploy-linux.yml
@@ -291,112 +291,112 @@ jobs:
# Upgrade pip
python3 -m pip install --upgrade pip
- - name: Enable Public Network Access for Data Upload (WAF Only)
- if: inputs.WAF_ENABLED == 'true'
- shell: bash
- env:
- INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
- INPUT_EXP: ${{ inputs.EXP }}
- INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
- run: |
- echo "๐ Temporarily enabling public network access for data upload..."
- SUBSCRIPTION_ID=$(az account show --query id -o tsv)
- SP_OBJECT_ID=$(az ad sp show --id ${{ secrets.AZURE_CLIENT_ID }} --query id -o tsv)
-
- # Get Cosmos DB account name and enable public access
- COSMOS_ACCOUNT=$(az cosmosdb list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
- if [[ -n "$COSMOS_ACCOUNT" ]]; then
- echo "Enabling public access for Cosmos DB: $COSMOS_ACCOUNT"
- az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Enabled
+ # - name: Enable Public Network Access for Data Upload (WAF Only)
+ # if: inputs.WAF_ENABLED == 'true'
+ # shell: bash
+ # env:
+ # INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ # INPUT_EXP: ${{ inputs.EXP }}
+ # INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
+ # run: |
+ # echo "๐ Temporarily enabling public network access for data upload..."
+ # SUBSCRIPTION_ID=$(az account show --query id -o tsv)
+ # SP_OBJECT_ID=$(az ad sp show --id ${{ secrets.AZURE_CLIENT_ID }} --query id -o tsv)
+
+ # # Get Cosmos DB account name and enable public access
+ # COSMOS_ACCOUNT=$(az cosmosdb list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
+ # if [[ -n "$COSMOS_ACCOUNT" ]]; then
+ # echo "Enabling public access for Cosmos DB: $COSMOS_ACCOUNT"
+ # az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Enabled
- # Get GitHub Actions runner public IP and add to firewall
- RUNNER_IP=$(curl -s https://api.ipify.org)
- echo "GitHub Actions Runner IP: $RUNNER_IP"
- echo "Adding runner IP to Cosmos DB firewall rules..."
- az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" \
- --ip-range-filter "$RUNNER_IP"
- echo "โ
Cosmos DB public access enabled and runner IP added to firewall"
- else
- echo "โ ๏ธ No Cosmos DB account found in resource group"
- fi
-
- # Get AI Services account - check current RG first, then extract from EXP project ID if needed
- AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[?kind=='AIServices'].name | [0]" -o tsv)
- AI_SERVICES_RG="$INPUT_RESOURCE_GROUP_NAME"
-
- # If EXP mode, extract AI Services account name directly from the project resource ID
- if [[ -z "$AI_SERVICES_ACCOUNT" && "$INPUT_EXP" == "true" ]]; then
- echo "EXP mode: Extracting AI Services from existing AI project resource ID..."
- if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
- EXP_PROJECT_ID="$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID"
- else
- EXP_PROJECT_ID="${{ vars.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}"
- fi
- # Extract resource group: .../resourceGroups/{rg}/...
- EXP_RG=$(echo "$EXP_PROJECT_ID" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
- # Extract account name: .../accounts/{accountName}/projects/...
- EXP_ACCOUNT=$(echo "$EXP_PROJECT_ID" | sed -n 's|.*/accounts/\([^/]*\)/.*|\1|p')
- if [[ -n "$EXP_ACCOUNT" && -n "$EXP_RG" ]]; then
- AI_SERVICES_ACCOUNT="$EXP_ACCOUNT"
- AI_SERVICES_RG="$EXP_RG"
- echo "Found AI Services from EXP project: $AI_SERVICES_ACCOUNT (RG: $AI_SERVICES_RG)"
- fi
- fi
-
- if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
- echo "Enabling public access for AI Services: $AI_SERVICES_ACCOUNT (RG: $AI_SERVICES_RG)"
- az rest --method PATCH \
- --url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${AI_SERVICES_RG}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
- --body '{"properties":{"publicNetworkAccess":"Enabled"}}'
- echo "โ
AI Services public access enabled"
+ # # Get GitHub Actions runner public IP and add to firewall
+ # RUNNER_IP=$(curl -s https://api.ipify.org)
+ # echo "GitHub Actions Runner IP: $RUNNER_IP"
+ # echo "Adding runner IP to Cosmos DB firewall rules..."
+ # az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" \
+ # --ip-range-filter "$RUNNER_IP"
+ # echo "โ
Cosmos DB public access enabled and runner IP added to firewall"
+ # else
+ # echo "โ ๏ธ No Cosmos DB account found in resource group"
+ # fi
+
+ # # Get AI Services account - check current RG first, then extract from EXP project ID if needed
+ # AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[?kind=='AIServices'].name | [0]" -o tsv)
+ # AI_SERVICES_RG="$INPUT_RESOURCE_GROUP_NAME"
+
+ # # If EXP mode, extract AI Services account name directly from the project resource ID
+ # if [[ -z "$AI_SERVICES_ACCOUNT" && "$INPUT_EXP" == "true" ]]; then
+ # echo "EXP mode: Extracting AI Services from existing AI project resource ID..."
+ # if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
+ # EXP_PROJECT_ID="$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID"
+ # else
+ # EXP_PROJECT_ID="${{ vars.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}"
+ # fi
+ # # Extract resource group: .../resourceGroups/{rg}/...
+ # EXP_RG=$(echo "$EXP_PROJECT_ID" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ # # Extract account name: .../accounts/{accountName}/projects/...
+ # EXP_ACCOUNT=$(echo "$EXP_PROJECT_ID" | sed -n 's|.*/accounts/\([^/]*\)/.*|\1|p')
+ # if [[ -n "$EXP_ACCOUNT" && -n "$EXP_RG" ]]; then
+ # AI_SERVICES_ACCOUNT="$EXP_ACCOUNT"
+ # AI_SERVICES_RG="$EXP_RG"
+ # echo "Found AI Services from EXP project: $AI_SERVICES_ACCOUNT (RG: $AI_SERVICES_RG)"
+ # fi
+ # fi
+
+ # if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
+ # echo "Enabling public access for AI Services: $AI_SERVICES_ACCOUNT (RG: $AI_SERVICES_RG)"
+ # az rest --method PATCH \
+ # --url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${AI_SERVICES_RG}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
+ # --body '{"properties":{"publicNetworkAccess":"Enabled"}}'
+ # echo "โ
AI Services public access enabled"
- # Assign Cognitive Services roles to service principal for embeddings and OpenAI access
- echo "Assigning Cognitive Services roles to service principal..."
- AI_SERVICES_ID=$(az cognitiveservices account show --name "$AI_SERVICES_ACCOUNT" --resource-group "$AI_SERVICES_RG" --query id -o tsv)
- az role assignment create --assignee-object-id "$SP_OBJECT_ID" --assignee-principal-type ServicePrincipal \
- --role "Cognitive Services OpenAI User" --scope "$AI_SERVICES_ID" 2>/dev/null || echo "OpenAI User role may already be assigned"
- az role assignment create --assignee-object-id "$SP_OBJECT_ID" --assignee-principal-type ServicePrincipal \
- --role "Cognitive Services User" --scope "$AI_SERVICES_ID" 2>/dev/null || echo "Cognitive Services User role may already be assigned"
- echo "โ
Cognitive Services roles assigned"
- else
- echo "โ ๏ธ No AI Services account found"
- fi
-
- # Get AI Search service and enable public access
- SEARCH_SERVICE=$(az search service list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
- if [[ -n "$SEARCH_SERVICE" ]]; then
- echo "Enabling public access for AI Search: $SEARCH_SERVICE"
- az search service update --name "$SEARCH_SERVICE" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-access enabled
- echo "โ
AI Search public access enabled"
- else
- echo "โ ๏ธ No AI Search service found in resource group"
- fi
-
- # Wait for network changes to propagate (5 minutes for Cosmos DB with private endpoints)
- echo "โณ Waiting 5 minutes for Cosmos DB network changes to propagate..."
- sleep 300
-
- # Verify Cosmos DB is accessible before proceeding
- if [[ -n "$COSMOS_ACCOUNT" ]]; then
- echo "๐ Verifying Cosmos DB connectivity..."
- COSMOS_ENDPOINT="https://${COSMOS_ACCOUNT}.documents.azure.com:443/"
- MAX_RETRIES=10
- RETRY_COUNT=0
- while [[ $RETRY_COUNT -lt $MAX_RETRIES ]]; do
- HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${COSMOS_ENDPOINT}" --max-time 10 2>/dev/null || echo "000")
- if [[ "$HTTP_CODE" == "401" || "$HTTP_CODE" == "200" ]]; then
- echo "โ
Cosmos DB is accessible (HTTP $HTTP_CODE - auth required, network OK)"
- break
- fi
- RETRY_COUNT=$((RETRY_COUNT + 1))
- echo "โณ Cosmos DB not yet accessible (HTTP $HTTP_CODE), retry $RETRY_COUNT/$MAX_RETRIES..."
- sleep 30
- done
- if [[ $RETRY_COUNT -eq $MAX_RETRIES ]]; then
- echo "โ ๏ธ Cosmos DB connectivity check timed out, proceeding anyway..."
- fi
- fi
- echo "โ
Network changes should now be propagated"
+ # # Assign Cognitive Services roles to service principal for embeddings and OpenAI access
+ # echo "Assigning Cognitive Services roles to service principal..."
+ # AI_SERVICES_ID=$(az cognitiveservices account show --name "$AI_SERVICES_ACCOUNT" --resource-group "$AI_SERVICES_RG" --query id -o tsv)
+ # az role assignment create --assignee-object-id "$SP_OBJECT_ID" --assignee-principal-type ServicePrincipal \
+ # --role "Cognitive Services OpenAI User" --scope "$AI_SERVICES_ID" 2>/dev/null || echo "OpenAI User role may already be assigned"
+ # az role assignment create --assignee-object-id "$SP_OBJECT_ID" --assignee-principal-type ServicePrincipal \
+ # --role "Cognitive Services User" --scope "$AI_SERVICES_ID" 2>/dev/null || echo "Cognitive Services User role may already be assigned"
+ # echo "โ
Cognitive Services roles assigned"
+ # else
+ # echo "โ ๏ธ No AI Services account found"
+ # fi
+
+ # # Get AI Search service and enable public access
+ # SEARCH_SERVICE=$(az search service list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
+ # if [[ -n "$SEARCH_SERVICE" ]]; then
+ # echo "Enabling public access for AI Search: $SEARCH_SERVICE"
+ # az search service update --name "$SEARCH_SERVICE" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-access enabled
+ # echo "โ
AI Search public access enabled"
+ # else
+ # echo "โ ๏ธ No AI Search service found in resource group"
+ # fi
+
+ # # Wait for network changes to propagate (5 minutes for Cosmos DB with private endpoints)
+ # echo "โณ Waiting 5 minutes for Cosmos DB network changes to propagate..."
+ # sleep 300
+
+ # # Verify Cosmos DB is accessible before proceeding
+ # if [[ -n "$COSMOS_ACCOUNT" ]]; then
+ # echo "๐ Verifying Cosmos DB connectivity..."
+ # COSMOS_ENDPOINT="https://${COSMOS_ACCOUNT}.documents.azure.com:443/"
+ # MAX_RETRIES=10
+ # RETRY_COUNT=0
+ # while [[ $RETRY_COUNT -lt $MAX_RETRIES ]]; do
+ # HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${COSMOS_ENDPOINT}" --max-time 10 2>/dev/null || echo "000")
+ # if [[ "$HTTP_CODE" == "401" || "$HTTP_CODE" == "200" ]]; then
+ # echo "โ
Cosmos DB is accessible (HTTP $HTTP_CODE - auth required, network OK)"
+ # break
+ # fi
+ # RETRY_COUNT=$((RETRY_COUNT + 1))
+ # echo "โณ Cosmos DB not yet accessible (HTTP $HTTP_CODE), retry $RETRY_COUNT/$MAX_RETRIES..."
+ # sleep 30
+ # done
+ # if [[ $RETRY_COUNT -eq $MAX_RETRIES ]]; then
+ # echo "โ ๏ธ Cosmos DB connectivity check timed out, proceeding anyway..."
+ # fi
+ # fi
+ # echo "โ
Network changes should now be propagated"
- name: Run Data Upload Scripts
shell: bash
@@ -448,62 +448,62 @@ jobs:
sleep 600
echo "โ
App readiness wait completed"
- - name: Restore Private Network Access (WAF Only)
- if: always() && inputs.WAF_ENABLED == 'true'
- shell: bash
- env:
- INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
- INPUT_EXP: ${{ inputs.EXP }}
- INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
- run: |
- echo "๐ Restoring private network access settings..."
- SUBSCRIPTION_ID=$(az account show --query id -o tsv 2>/dev/null)
-
- # Get Cosmos DB account name
- COSMOS_ACCOUNT=$(az cosmosdb list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
- if [[ -n "$COSMOS_ACCOUNT" ]]; then
- echo "Disabling public access for Cosmos DB: $COSMOS_ACCOUNT"
- # Clear IP rules first
- az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --ip-range-filter "" || true
- # Then disable public network access
- az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Disabled || true
- echo "โ
Cosmos DB public access disabled and firewall rules cleared"
- fi
-
- # Get AI Services account - check current RG first, then EXP RG if needed
- AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
- AI_SERVICES_RG="$INPUT_RESOURCE_GROUP_NAME"
-
- # If EXP mode and no AI Services in current RG, get from existing AI project RG
- if [[ -z "$AI_SERVICES_ACCOUNT" && "$INPUT_EXP" == "true" ]]; then
- if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
- EXP_RG=$(echo "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
- else
- EXP_RG=$(echo "${{ vars.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
- fi
- if [[ -n "$EXP_RG" ]]; then
- AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$EXP_RG" --query "[0].name" -o tsv 2>/dev/null)
- AI_SERVICES_RG="$EXP_RG"
- fi
- fi
-
- if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
- echo "Disabling public access for AI Services: $AI_SERVICES_ACCOUNT (RG: $AI_SERVICES_RG)"
- az rest --method PATCH \
- --url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${AI_SERVICES_RG}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
- --body '{"properties":{"publicNetworkAccess":"Disabled"}}' || true
- echo "โ
AI Services public access disabled"
- fi
-
- # Get AI Search service and disable public access
- SEARCH_SERVICE=$(az search service list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
- if [[ -n "$SEARCH_SERVICE" ]]; then
- echo "Disabling public access for AI Search: $SEARCH_SERVICE"
- az search service update --name "$SEARCH_SERVICE" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-access disabled || true
- echo "โ
AI Search public access disabled"
- fi
-
- echo "๐ Private network access restored"
+ # - name: Restore Private Network Access (WAF Only)
+ # if: always() && inputs.WAF_ENABLED == 'true'
+ # shell: bash
+ # env:
+ # INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ # INPUT_EXP: ${{ inputs.EXP }}
+ # INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
+ # run: |
+ # echo "๐ Restoring private network access settings..."
+ # SUBSCRIPTION_ID=$(az account show --query id -o tsv 2>/dev/null)
+
+ # # Get Cosmos DB account name
+ # COSMOS_ACCOUNT=$(az cosmosdb list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
+ # if [[ -n "$COSMOS_ACCOUNT" ]]; then
+ # echo "Disabling public access for Cosmos DB: $COSMOS_ACCOUNT"
+ # # Clear IP rules first
+ # az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --ip-range-filter "" || true
+ # # Then disable public network access
+ # az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Disabled || true
+ # echo "โ
Cosmos DB public access disabled and firewall rules cleared"
+ # fi
+
+ # # Get AI Services account - check current RG first, then EXP RG if needed
+ # AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
+ # AI_SERVICES_RG="$INPUT_RESOURCE_GROUP_NAME"
+
+ # # If EXP mode and no AI Services in current RG, get from existing AI project RG
+ # if [[ -z "$AI_SERVICES_ACCOUNT" && "$INPUT_EXP" == "true" ]]; then
+ # if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
+ # EXP_RG=$(echo "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ # else
+ # EXP_RG=$(echo "${{ vars.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ # fi
+ # if [[ -n "$EXP_RG" ]]; then
+ # AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$EXP_RG" --query "[0].name" -o tsv 2>/dev/null)
+ # AI_SERVICES_RG="$EXP_RG"
+ # fi
+ # fi
+
+ # if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
+ # echo "Disabling public access for AI Services: $AI_SERVICES_ACCOUNT (RG: $AI_SERVICES_RG)"
+ # az rest --method PATCH \
+ # --url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${AI_SERVICES_RG}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
+ # --body '{"properties":{"publicNetworkAccess":"Disabled"}}' || true
+ # echo "โ
AI Services public access disabled"
+ # fi
+
+ # # Get AI Search service and disable public access
+ # SEARCH_SERVICE=$(az search service list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
+ # if [[ -n "$SEARCH_SERVICE" ]]; then
+ # echo "Disabling public access for AI Search: $SEARCH_SERVICE"
+ # az search service update --name "$SEARCH_SERVICE" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-access disabled || true
+ # echo "โ
AI Search public access disabled"
+ # fi
+
+ # echo "๐ Private network access restored"
- name: Generate Deployment Summary
if: always()
diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml
index b946910a..00a5993b 100644
--- a/.github/workflows/job-deploy-windows.yml
+++ b/.github/workflows/job-deploy-windows.yml
@@ -289,112 +289,112 @@ jobs:
"WEBAPP_URL=$WEBAPP_URL" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
"WEBAPP_URL=$WEBAPP_URL" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
- - name: Enable Public Network Access for Data Upload (WAF Only)
- if: inputs.WAF_ENABLED == 'true'
- shell: bash
- env:
- INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
- INPUT_EXP: ${{ inputs.EXP }}
- INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
- run: |
- echo "๐ Temporarily enabling public network access for data upload..."
- SUBSCRIPTION_ID=$(az account show --query id -o tsv)
- SP_OBJECT_ID=$(az ad sp show --id ${{ secrets.AZURE_CLIENT_ID }} --query id -o tsv)
+ # - name: Enable Public Network Access for Data Upload (WAF Only)
+ # if: inputs.WAF_ENABLED == 'true'
+ # shell: bash
+ # env:
+ # INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ # INPUT_EXP: ${{ inputs.EXP }}
+ # INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
+ # run: |
+ # echo "๐ Temporarily enabling public network access for data upload..."
+ # SUBSCRIPTION_ID=$(az account show --query id -o tsv)
+ # SP_OBJECT_ID=$(az ad sp show --id ${{ secrets.AZURE_CLIENT_ID }} --query id -o tsv)
- # Get Cosmos DB account name and enable public access
- COSMOS_ACCOUNT=$(az cosmosdb list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
- if [[ -n "$COSMOS_ACCOUNT" ]]; then
- echo "Enabling public access for Cosmos DB: $COSMOS_ACCOUNT"
- az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Enabled
+ # # Get Cosmos DB account name and enable public access
+ # COSMOS_ACCOUNT=$(az cosmosdb list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
+ # if [[ -n "$COSMOS_ACCOUNT" ]]; then
+ # echo "Enabling public access for Cosmos DB: $COSMOS_ACCOUNT"
+ # az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Enabled
- # Get GitHub Actions runner public IP and add to firewall
- RUNNER_IP=$(curl -s https://api.ipify.org)
- echo "GitHub Actions Runner IP: $RUNNER_IP"
- echo "Adding runner IP to Cosmos DB firewall rules..."
- az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" \
- --ip-range-filter "$RUNNER_IP"
- echo "โ
Cosmos DB public access enabled and runner IP added to firewall"
- else
- echo "โ ๏ธ No Cosmos DB account found in resource group"
- fi
+ # # Get GitHub Actions runner public IP and add to firewall
+ # RUNNER_IP=$(curl -s https://api.ipify.org)
+ # echo "GitHub Actions Runner IP: $RUNNER_IP"
+ # echo "Adding runner IP to Cosmos DB firewall rules..."
+ # az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" \
+ # --ip-range-filter "$RUNNER_IP"
+ # echo "โ
Cosmos DB public access enabled and runner IP added to firewall"
+ # else
+ # echo "โ ๏ธ No Cosmos DB account found in resource group"
+ # fi
- # Get AI Services account - check current RG first, then extract from EXP project ID if needed
- AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[?kind=='AIServices'].name | [0]" -o tsv)
- AI_SERVICES_RG="$INPUT_RESOURCE_GROUP_NAME"
+ # # Get AI Services account - check current RG first, then extract from EXP project ID if needed
+ # AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[?kind=='AIServices'].name | [0]" -o tsv)
+ # AI_SERVICES_RG="$INPUT_RESOURCE_GROUP_NAME"
- # If EXP mode, extract AI Services account name directly from the project resource ID
- if [[ -z "$AI_SERVICES_ACCOUNT" && "$INPUT_EXP" == "true" ]]; then
- echo "EXP mode: Extracting AI Services from existing AI project resource ID..."
- if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
- EXP_PROJECT_ID="$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID"
- else
- EXP_PROJECT_ID="${{ vars.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}"
- fi
- # Extract resource group: .../resourceGroups/{rg}/...
- EXP_RG=$(echo "$EXP_PROJECT_ID" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
- # Extract account name: .../accounts/{accountName}/projects/...
- EXP_ACCOUNT=$(echo "$EXP_PROJECT_ID" | sed -n 's|.*/accounts/\([^/]*\)/.*|\1|p')
- if [[ -n "$EXP_ACCOUNT" && -n "$EXP_RG" ]]; then
- AI_SERVICES_ACCOUNT="$EXP_ACCOUNT"
- AI_SERVICES_RG="$EXP_RG"
- echo "Found AI Services from EXP project: $AI_SERVICES_ACCOUNT (RG: $AI_SERVICES_RG)"
- fi
- fi
+ # # If EXP mode, extract AI Services account name directly from the project resource ID
+ # if [[ -z "$AI_SERVICES_ACCOUNT" && "$INPUT_EXP" == "true" ]]; then
+ # echo "EXP mode: Extracting AI Services from existing AI project resource ID..."
+ # if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
+ # EXP_PROJECT_ID="$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID"
+ # else
+ # EXP_PROJECT_ID="${{ vars.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}"
+ # fi
+ # # Extract resource group: .../resourceGroups/{rg}/...
+ # EXP_RG=$(echo "$EXP_PROJECT_ID" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ # # Extract account name: .../accounts/{accountName}/projects/...
+ # EXP_ACCOUNT=$(echo "$EXP_PROJECT_ID" | sed -n 's|.*/accounts/\([^/]*\)/.*|\1|p')
+ # if [[ -n "$EXP_ACCOUNT" && -n "$EXP_RG" ]]; then
+ # AI_SERVICES_ACCOUNT="$EXP_ACCOUNT"
+ # AI_SERVICES_RG="$EXP_RG"
+ # echo "Found AI Services from EXP project: $AI_SERVICES_ACCOUNT (RG: $AI_SERVICES_RG)"
+ # fi
+ # fi
- if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
- echo "Enabling public access for AI Services: $AI_SERVICES_ACCOUNT (RG: $AI_SERVICES_RG)"
- az rest --method PATCH \
- --url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${AI_SERVICES_RG}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
- --body '{"properties":{"publicNetworkAccess":"Enabled"}}'
- echo "โ
AI Services public access enabled"
+ # if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
+ # echo "Enabling public access for AI Services: $AI_SERVICES_ACCOUNT (RG: $AI_SERVICES_RG)"
+ # az rest --method PATCH \
+ # --url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${AI_SERVICES_RG}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
+ # --body '{"properties":{"publicNetworkAccess":"Enabled"}}'
+ # echo "โ
AI Services public access enabled"
- # Assign Cognitive Services roles to service principal for embeddings and OpenAI access
- echo "Assigning Cognitive Services roles to service principal..."
- AI_SERVICES_ID=$(az cognitiveservices account show --name "$AI_SERVICES_ACCOUNT" --resource-group "$AI_SERVICES_RG" --query id -o tsv)
- az role assignment create --assignee-object-id "$SP_OBJECT_ID" --assignee-principal-type ServicePrincipal \
- --role "Cognitive Services OpenAI User" --scope "$AI_SERVICES_ID" 2>/dev/null || echo "OpenAI User role may already be assigned"
- az role assignment create --assignee-object-id "$SP_OBJECT_ID" --assignee-principal-type ServicePrincipal \
- --role "Cognitive Services User" --scope "$AI_SERVICES_ID" 2>/dev/null || echo "Cognitive Services User role may already be assigned"
- echo "โ
Cognitive Services roles assigned"
- else
- echo "โ ๏ธ No AI Services account found"
- fi
+ # # Assign Cognitive Services roles to service principal for embeddings and OpenAI access
+ # echo "Assigning Cognitive Services roles to service principal..."
+ # AI_SERVICES_ID=$(az cognitiveservices account show --name "$AI_SERVICES_ACCOUNT" --resource-group "$AI_SERVICES_RG" --query id -o tsv)
+ # az role assignment create --assignee-object-id "$SP_OBJECT_ID" --assignee-principal-type ServicePrincipal \
+ # --role "Cognitive Services OpenAI User" --scope "$AI_SERVICES_ID" 2>/dev/null || echo "OpenAI User role may already be assigned"
+ # az role assignment create --assignee-object-id "$SP_OBJECT_ID" --assignee-principal-type ServicePrincipal \
+ # --role "Cognitive Services User" --scope "$AI_SERVICES_ID" 2>/dev/null || echo "Cognitive Services User role may already be assigned"
+ # echo "โ
Cognitive Services roles assigned"
+ # else
+ # echo "โ ๏ธ No AI Services account found"
+ # fi
- # Get AI Search service and enable public access
- SEARCH_SERVICE=$(az search service list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
- if [[ -n "$SEARCH_SERVICE" ]]; then
- echo "Enabling public access for AI Search: $SEARCH_SERVICE"
- az search service update --name "$SEARCH_SERVICE" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-access enabled
- echo "โ
AI Search public access enabled"
- else
- echo "โ ๏ธ No AI Search service found in resource group"
- fi
+ # # Get AI Search service and enable public access
+ # SEARCH_SERVICE=$(az search service list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv)
+ # if [[ -n "$SEARCH_SERVICE" ]]; then
+ # echo "Enabling public access for AI Search: $SEARCH_SERVICE"
+ # az search service update --name "$SEARCH_SERVICE" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-access enabled
+ # echo "โ
AI Search public access enabled"
+ # else
+ # echo "โ ๏ธ No AI Search service found in resource group"
+ # fi
- # Wait for network changes to propagate (5 minutes for Cosmos DB with private endpoints)
- echo "โณ Waiting 5 minutes for Cosmos DB network changes to propagate..."
- sleep 300
+ # # Wait for network changes to propagate (5 minutes for Cosmos DB with private endpoints)
+ # echo "โณ Waiting 5 minutes for Cosmos DB network changes to propagate..."
+ # sleep 300
- # Verify Cosmos DB is accessible before proceeding
- if [[ -n "$COSMOS_ACCOUNT" ]]; then
- echo "๐ Verifying Cosmos DB connectivity..."
- COSMOS_ENDPOINT="https://${COSMOS_ACCOUNT}.documents.azure.com:443/"
- MAX_RETRIES=10
- RETRY_COUNT=0
- while [[ $RETRY_COUNT -lt $MAX_RETRIES ]]; do
- HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${COSMOS_ENDPOINT}" --max-time 10 2>/dev/null || echo "000")
- if [[ "$HTTP_CODE" == "401" || "$HTTP_CODE" == "200" ]]; then
- echo "โ
Cosmos DB is accessible (HTTP $HTTP_CODE - auth required, network OK)"
- break
- fi
- RETRY_COUNT=$((RETRY_COUNT + 1))
- echo "โณ Cosmos DB not yet accessible (HTTP $HTTP_CODE), retry $RETRY_COUNT/$MAX_RETRIES..."
- sleep 30
- done
- if [[ $RETRY_COUNT -eq $MAX_RETRIES ]]; then
- echo "โ ๏ธ Cosmos DB connectivity check timed out, proceeding anyway..."
- fi
- fi
- echo "โ
Network changes should now be propagated"
+ # # Verify Cosmos DB is accessible before proceeding
+ # if [[ -n "$COSMOS_ACCOUNT" ]]; then
+ # echo "๐ Verifying Cosmos DB connectivity..."
+ # COSMOS_ENDPOINT="https://${COSMOS_ACCOUNT}.documents.azure.com:443/"
+ # MAX_RETRIES=10
+ # RETRY_COUNT=0
+ # while [[ $RETRY_COUNT -lt $MAX_RETRIES ]]; do
+ # HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${COSMOS_ENDPOINT}" --max-time 10 2>/dev/null || echo "000")
+ # if [[ "$HTTP_CODE" == "401" || "$HTTP_CODE" == "200" ]]; then
+ # echo "โ
Cosmos DB is accessible (HTTP $HTTP_CODE - auth required, network OK)"
+ # break
+ # fi
+ # RETRY_COUNT=$((RETRY_COUNT + 1))
+ # echo "โณ Cosmos DB not yet accessible (HTTP $HTTP_CODE), retry $RETRY_COUNT/$MAX_RETRIES..."
+ # sleep 30
+ # done
+ # if [[ $RETRY_COUNT -eq $MAX_RETRIES ]]; then
+ # echo "โ ๏ธ Cosmos DB connectivity check timed out, proceeding anyway..."
+ # fi
+ # fi
+ # echo "โ
Network changes should now be propagated"
- name: Run Post-Deployment Data Upload Scripts
shell: pwsh
@@ -446,62 +446,62 @@ jobs:
sleep 600
echo "โ
App readiness wait completed"
- - name: Restore Private Network Access (WAF Only)
- if: always() && inputs.WAF_ENABLED == 'true'
- shell: bash
- env:
- INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
- INPUT_EXP: ${{ inputs.EXP }}
- INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
- run: |
- echo "๐ Restoring private network access settings..."
- SUBSCRIPTION_ID=$(az account show --query id -o tsv 2>/dev/null)
+ # - name: Restore Private Network Access (WAF Only)
+ # if: always() && inputs.WAF_ENABLED == 'true'
+ # shell: bash
+ # env:
+ # INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ # INPUT_EXP: ${{ inputs.EXP }}
+ # INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
+ # run: |
+ # echo "๐ Restoring private network access settings..."
+ # SUBSCRIPTION_ID=$(az account show --query id -o tsv 2>/dev/null)
- # Get Cosmos DB account name
- COSMOS_ACCOUNT=$(az cosmosdb list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
- if [[ -n "$COSMOS_ACCOUNT" ]]; then
- echo "Disabling public access for Cosmos DB: $COSMOS_ACCOUNT"
- # Clear IP rules first
- az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --ip-range-filter "" || true
- # Then disable public network access
- az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Disabled || true
- echo "โ
Cosmos DB public access disabled and firewall rules cleared"
- fi
+ # # Get Cosmos DB account name
+ # COSMOS_ACCOUNT=$(az cosmosdb list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
+ # if [[ -n "$COSMOS_ACCOUNT" ]]; then
+ # echo "Disabling public access for Cosmos DB: $COSMOS_ACCOUNT"
+ # # Clear IP rules first
+ # az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --ip-range-filter "" || true
+ # # Then disable public network access
+ # az cosmosdb update --name "$COSMOS_ACCOUNT" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-network-access Disabled || true
+ # echo "โ
Cosmos DB public access disabled and firewall rules cleared"
+ # fi
- # Get AI Services account - check current RG first, then EXP RG if needed
- AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
- AI_SERVICES_RG="$INPUT_RESOURCE_GROUP_NAME"
+ # # Get AI Services account - check current RG first, then EXP RG if needed
+ # AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
+ # AI_SERVICES_RG="$INPUT_RESOURCE_GROUP_NAME"
- # If EXP mode and no AI Services in current RG, get from existing AI project RG
- if [[ -z "$AI_SERVICES_ACCOUNT" && "$INPUT_EXP" == "true" ]]; then
- if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
- EXP_RG=$(echo "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
- else
- EXP_RG=$(echo "${{ vars.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
- fi
- if [[ -n "$EXP_RG" ]]; then
- AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$EXP_RG" --query "[0].name" -o tsv 2>/dev/null)
- AI_SERVICES_RG="$EXP_RG"
- fi
- fi
+ # # If EXP mode and no AI Services in current RG, get from existing AI project RG
+ # if [[ -z "$AI_SERVICES_ACCOUNT" && "$INPUT_EXP" == "true" ]]; then
+ # if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
+ # EXP_RG=$(echo "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ # else
+ # EXP_RG=$(echo "${{ vars.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ # fi
+ # if [[ -n "$EXP_RG" ]]; then
+ # AI_SERVICES_ACCOUNT=$(az cognitiveservices account list --resource-group "$EXP_RG" --query "[0].name" -o tsv 2>/dev/null)
+ # AI_SERVICES_RG="$EXP_RG"
+ # fi
+ # fi
- if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
- echo "Disabling public access for AI Services: $AI_SERVICES_ACCOUNT (RG: $AI_SERVICES_RG)"
- az rest --method PATCH \
- --url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${AI_SERVICES_RG}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
- --body '{"properties":{"publicNetworkAccess":"Disabled"}}' || true
- echo "โ
AI Services public access disabled"
- fi
+ # if [[ -n "$AI_SERVICES_ACCOUNT" ]]; then
+ # echo "Disabling public access for AI Services: $AI_SERVICES_ACCOUNT (RG: $AI_SERVICES_RG)"
+ # az rest --method PATCH \
+ # --url "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${AI_SERVICES_RG}/providers/Microsoft.CognitiveServices/accounts/${AI_SERVICES_ACCOUNT}?api-version=2024-10-01" \
+ # --body '{"properties":{"publicNetworkAccess":"Disabled"}}' || true
+ # echo "โ
AI Services public access disabled"
+ # fi
- # Get AI Search service and disable public access
- SEARCH_SERVICE=$(az search service list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
- if [[ -n "$SEARCH_SERVICE" ]]; then
- echo "Disabling public access for AI Search: $SEARCH_SERVICE"
- az search service update --name "$SEARCH_SERVICE" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-access disabled || true
- echo "โ
AI Search public access disabled"
- fi
+ # # Get AI Search service and disable public access
+ # SEARCH_SERVICE=$(az search service list --resource-group "$INPUT_RESOURCE_GROUP_NAME" --query "[0].name" -o tsv 2>/dev/null)
+ # if [[ -n "$SEARCH_SERVICE" ]]; then
+ # echo "Disabling public access for AI Search: $SEARCH_SERVICE"
+ # az search service update --name "$SEARCH_SERVICE" --resource-group "$INPUT_RESOURCE_GROUP_NAME" --public-access disabled || true
+ # echo "โ
AI Search public access disabled"
+ # fi
- echo "๐ Private network access restored"
+ # echo "๐ Private network access restored"
- name: Generate Deployment Summary
if: always()
diff --git a/infra/scripts/data_scripts/run_upload_data_scripts.sh b/infra/scripts/data_scripts/run_upload_data_scripts.sh
index 3002f693..adf36c44 100644
--- a/infra/scripts/data_scripts/run_upload_data_scripts.sh
+++ b/infra/scripts/data_scripts/run_upload_data_scripts.sh
@@ -64,6 +64,177 @@ if [ -z "$cosmosdb_account" ]; then
cosmosdb_account=$(azd env get-value AZURE_COSMOSDB_ACCOUNT)
fi
+original_foundry_public_access=""
+original_cosmos_public_access=""
+original_cosmos_ip_filter=""
+
+# Function to enable public network access temporarily
+enable_public_access() {
+ echo "=== Temporarily enabling public network access for services ==="
+
+ # Enable public access for Cosmos DB
+ echo "Configuring Cosmos DB network access: $cosmosdb_account"
+
+ # Get Cosmos DB resource ID
+ subscription_id=$(az account show --query id -o tsv)
+ cosmos_resource_id="/subscriptions/${subscription_id}/resourceGroups/${resource_group}/providers/Microsoft.DocumentDB/databaseAccounts/${cosmosdb_account}"
+
+ original_cosmos_public_access=$(MSYS_NO_PATHCONV=1 az resource show \
+ --ids "$cosmos_resource_id" \
+ --api-version 2021-04-15 \
+ --query "properties.publicNetworkAccess" \
+ --output tsv 2>/dev/null)
+ echo "Original Cosmos DB public access: $original_cosmos_public_access"
+
+ # Only modify Cosmos DB if it's not already enabled
+ if [ "$original_cosmos_public_access" = "Enabled" ]; then
+ echo "โ Cosmos DB public access already enabled - no changes needed"
+ else
+ echo "Getting current IP address..."
+ current_ip=$(curl -s https://ipinfo.io/ip)
+ echo "Current IP: $current_ip"
+ # Get current firewall rules and public network access setting
+ echo "Getting current firewall configuration..."
+ original_cosmos_ip_filter=$(MSYS_NO_PATHCONV=1 az resource show \
+ --ids "$cosmos_resource_id" \
+ --api-version 2021-04-15 \
+ --query "properties.ipRules" \
+ --output json 2>/dev/null || echo "[]")
+
+ echo "Cosmos DB public access is '$original_cosmos_public_access' - enabling access"
+
+ # Add current IP to firewall rules and enable public network access
+ echo "Adding current IP ($current_ip) to Cosmos DB firewall..."
+ if MSYS_NO_PATHCONV=1 az resource update \
+ --ids "$cosmos_resource_id" \
+ --api-version 2021-04-15 \
+ --set "properties.ipRules=[{\"ipAddressOrRange\":\"$current_ip\"}]" \
+ --set "properties.publicNetworkAccess=Enabled" \
+ --output none; then
+ echo "โ Cosmos DB firewall updated to allow current IP"
+ echo "โ Cosmos DB public network access enabled"
+
+ # Wait longer for changes to propagate
+ echo "Waiting for Cosmos DB network changes to take effect..."
+ sleep 30
+ echo "Network configuration should now be active"
+ else
+ echo "โ Warning: Failed to update Cosmos DB firewall. You may need to manually add IP $current_ip"
+ echo " Please add this IP address in Azure Portal: Cosmos DB > $cosmosdb_account > Networking > Firewall"
+ fi
+ fi
+
+ # Enable public access for AI Foundry
+ # Extract the account resource ID (remove /projects/... part if present)
+ aif_account_resource_id=$(echo "$aiFoundryResourceId" | sed 's|/projects/.*||')
+ aif_resource_name=$(basename "$aif_account_resource_id")
+ # Extract resource group from the AI Foundry account resource ID
+ aif_resource_group=$(echo "$aif_account_resource_id" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p')
+ # Extract subscription ID from the AI Foundry account resource ID
+ aif_subscription_id=$(echo "$aif_account_resource_id" | sed -n 's|.*/subscriptions/\([^/]*\)/.*|\1|p')
+
+ original_foundry_public_access=$(az cognitiveservices account show \
+ --name "$aif_resource_name" \
+ --resource-group "$aif_resource_group" \
+ --subscription "$aif_subscription_id" \
+ --query "properties.publicNetworkAccess" \
+ --output tsv)
+ if [ -z "$original_foundry_public_access" ] || [ "$original_foundry_public_access" = "null" ]; then
+ echo "โ Info: Could not retrieve AI Foundry network access status."
+ echo " AI Foundry network access might be managed differently."
+ elif [ "$original_foundry_public_access" != "Enabled" ]; then
+ echo "Current AI Foundry public access: $original_foundry_public_access"
+ echo "Enabling public access for AI Foundry resource: $aif_resource_name (Resource Group: $aif_resource_group)"
+ if MSYS_NO_PATHCONV=1 az resource update \
+ --ids "$aif_account_resource_id" \
+ --api-version 2024-10-01 \
+ --set properties.publicNetworkAccess=Enabled properties.apiProperties="{}" \
+ --output none; then
+ echo "โ AI Foundry public access enabled"
+ else
+ echo "โ Warning: Failed to enable AI Foundry public access automatically."
+ fi
+ else
+ echo "โ AI Foundry public access already enabled - no changes needed"
+ fi
+
+ # Wait a bit for changes to take effect
+ echo "Waiting for network access changes to propagate..."
+ sleep 10
+ echo "=== Public network access enabled successfully ==="
+ return 0
+}
+
+# Function to restore original network access settings
+restore_network_access() {
+ echo "=== Restoring original network access settings ==="
+
+ # Restore AI Foundry access only if it was changed from the original state
+ if [ -n "$original_foundry_public_access" ] && [ "$original_foundry_public_access" != "Enabled" ]; then
+ echo "Restoring AI Foundry public access to: $original_foundry_public_access"
+ # Reconstruct the AI Foundry resource ID for restoration
+ aif_account_resource_id=$(echo "$aiFoundryResourceId" | sed 's|/projects/.*||')
+ # Try using the working approach to restore the original setting
+ if MSYS_NO_PATHCONV=1 az resource update \
+ --ids "$aif_account_resource_id" \
+ --api-version 2024-10-01 \
+ --set properties.publicNetworkAccess="$original_foundry_public_access" \
+ --set properties.apiProperties.qnaAzureSearchEndpointKey="" \
+ --set properties.networkAcls.bypass="AzureServices" \
+ --output none 2>/dev/null; then
+ echo "โ AI Foundry access restored"
+ else
+ echo "โ Warning: Failed to restore AI Foundry access automatically."
+ echo " Please manually restore network access in the Azure portal if needed."
+ fi
+ else
+ echo "AI Foundry access unchanged (no restoration needed)"
+ fi
+
+ # Restore Cosmos DB settings only if it was changed from the original state
+ if [ -n "$original_cosmos_public_access" ] && [ "$original_cosmos_public_access" != "Enabled" ]; then
+ echo "Restoring Cosmos DB settings..."
+ subscription_id=$(az account show --query id -o tsv)
+ cosmos_resource_id="/subscriptions/${subscription_id}/resourceGroups/${resource_group}/providers/Microsoft.DocumentDB/databaseAccounts/${cosmosdb_account}"
+
+ # Prepare restore command with both firewall rules and public access setting
+ restore_command="--set properties.ipRules=$original_cosmos_ip_filter"
+ if [ -n "$original_cosmos_public_access" ] && [ "$original_cosmos_public_access" != "null" ]; then
+ restore_command="$restore_command --set properties.publicNetworkAccess=$original_cosmos_public_access"
+ echo "Restoring Cosmos DB public access to: $original_cosmos_public_access"
+ fi
+
+ if MSYS_NO_PATHCONV=1 az resource update \
+ --ids "$cosmos_resource_id" \
+ --api-version 2021-04-15 \
+ $restore_command \
+ --output none; then
+ echo "โ Cosmos DB settings restored"
+ else
+ echo "โ Warning: Failed to restore Cosmos DB settings automatically."
+ echo " Please manually check firewall and network settings in the Azure portal."
+ fi
+ else
+ echo "Cosmos DB unchanged (no restoration needed)"
+ fi
+
+ echo "=== Network access restoration completed ==="
+}
+
+# Function to handle script cleanup on exit
+cleanup_on_exit() {
+ exit_code=$?
+ echo ""
+ if [ $exit_code -ne 0 ]; then
+ echo "Script failed with exit code: $exit_code"
+ fi
+ echo "Performing cleanup..."
+ restore_network_access
+ exit $exit_code
+}
+
+# Set up trap to ensure cleanup happens on exit
+trap cleanup_on_exit EXIT INT TERM
# Check if user is logged in to Azure
echo "Checking Azure authentication..."
@@ -84,12 +255,12 @@ if [ -z "$signed_user_id" ]; then
echo "Not logged in as user, checking for service principal..."
account_info=$(az account show --query '{name:user.name, type:user.type}' -o json)
account_type=$(echo "$account_info" | jq -r '.type')
-
+
if [ "$account_type" = "servicePrincipal" ]; then
sp_name=$(echo "$account_info" | jq -r '.name')
echo "Logged in as service principal: $sp_name"
signed_user_id=$(az ad sp show --id "$sp_name" --query id -o tsv 2>/dev/null)
-
+
if [ -z "$signed_user_id" ]; then
echo "Warning: Could not get service principal object ID. Attempting to continue without role assignment..."
echo "Note: Ensure the service principal has necessary permissions assigned at subscription/resource group level."
@@ -105,12 +276,12 @@ else
echo "Logged in as user: $signed_user_id"
fi
-echo "Checking if the principal has Search roles on the AI Search Service"
-# search service contributor role id: 7ca78c08-252a-4471-8644-bb5ff32d4ba0
-# search index data contributor role id: 8ebe5a00-799e-43f5-93ac-243d3dce84a7
-# search index data reader role id: 1407120a-92aa-4202-b7e9-c0e197c71c8f
+if [ "$SKIP_ROLE_ASSIGNMENT" != "true" ]; then
+ echo "Checking if the principal has Search roles on the AI Search Service"
+ # search service contributor role id: 7ca78c08-252a-4471-8644-bb5ff32d4ba0
+ # search index data contributor role id: 8ebe5a00-799e-43f5-93ac-243d3dce84a7
+ # search index data reader role id: 1407120a-92aa-4202-b7e9-c0e197c71c8f
-if [ "$SKIP_ROLE_ASSIGNMENT" != "true" ] && [ -n "$signed_user_id" ]; then
role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list \
--role "7ca78c08-252a-4471-8644-bb5ff32d4ba0" \
--scope "$aiSearchResourceId" \
@@ -158,11 +329,7 @@ if [ "$SKIP_ROLE_ASSIGNMENT" != "true" ] && [ -n "$signed_user_id" ]; then
else
echo "Principal already has the search index data contributor role."
fi
-else
- echo "Skipping role assignment (will rely on existing permissions)"
-fi
-if [ "$SKIP_ROLE_ASSIGNMENT" != "true" ] && [ -n "$signed_user_id" ]; then
role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list \
--role "1407120a-92aa-4202-b7e9-c0e197c71c8f" \
--scope "$aiSearchResourceId" \
@@ -186,10 +353,12 @@ if [ "$SKIP_ROLE_ASSIGNMENT" != "true" ] && [ -n "$signed_user_id" ]; then
else
echo "Principal already has the search index data reader role."
fi
+else
+ echo "Skipping role assignments - assuming permissions are pre-configured"
fi
# Check if the principal has the Cosmos DB Built-in Data Contributor role
-if [ "$SKIP_ROLE_ASSIGNMENT" != "true" ] && [ -n "$signed_user_id" ]; then
+if [ "$SKIP_ROLE_ASSIGNMENT" != "true" ]; then
echo "Checking if principal has the Cosmos DB Built-in Data Contributor role"
roleExists=$(az cosmosdb sql role assignment list \
--resource-group $resource_group \
@@ -215,7 +384,7 @@ if [ "$SKIP_ROLE_ASSIGNMENT" != "true" ] && [ -n "$signed_user_id" ]; then
fi
fi
else
- echo "Skipping Cosmos DB role assignment (will rely on existing permissions)"
+ echo "Skipping Cosmos DB role assignment - assuming permissions are pre-configured"
fi
# role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list \
@@ -279,8 +448,16 @@ requirementFile="infra/scripts/data_scripts/requirements.txt"
python -m pip install --upgrade pip
python -m pip install --quiet -r "$requirementFile"
+# Enable public network access for required services
+enable_public_access
+if [ $? -ne 0 ]; then
+ echo "Error: Failed to enable public network access for services."
+ exit 1
+fi
# python pip install -r infra/scripts/data_scripts/requirements.txt --quiet
python infra/scripts/data_scripts/01_create_products_search_index.py --ai_search_endpoint="$ai_search_endpoint" --azure_openai_endpoint="$azure_openai_endpoint" --embedding_model_name="$embedding_model_name"
python infra/scripts/data_scripts/02_create_policies_search_index.py --ai_search_endpoint="$ai_search_endpoint" --azure_openai_endpoint="$azure_openai_endpoint" --embedding_model_name="$embedding_model_name"
-python infra/scripts/data_scripts/03_write_products_to_cosmos.py --cosmosdb_account="$cosmosdb_account"
\ No newline at end of file
+python infra/scripts/data_scripts/03_write_products_to_cosmos.py --cosmosdb_account="$cosmosdb_account"
+
+echo "Network access will be restored to original settings..."
\ No newline at end of file
From 082025641951bec6a28c85587a7fe1d0ebd29b1f Mon Sep 17 00:00:00 2001
From: Ajit Padhi
Date: Thu, 5 Feb 2026 11:37:50 +0530
Subject: [PATCH 31/38] Fixed postdeployment authentication issue
---
infra/scripts/azure_credential_utils.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/infra/scripts/azure_credential_utils.py b/infra/scripts/azure_credential_utils.py
index 64fd1f4d..862a161d 100644
--- a/infra/scripts/azure_credential_utils.py
+++ b/infra/scripts/azure_credential_utils.py
@@ -1,4 +1,4 @@
-from azure.identity import DefaultAzureCredential, ManagedIdentityCredential
+from azure.identity import AzureCliCredential, ManagedIdentityCredential
APP_ENV = "dev" # Change to 'dev' for local development
@@ -19,7 +19,7 @@ def get_azure_credential(client_id=None):
"""
if APP_ENV == "dev":
return (
- DefaultAzureCredential()
- ) # CodeQL [SM05139] Okay use of DefaultAzureCredential as it is only used in development
+ AzureCliCredential()
+ ) # CodeQL [SM05139] Okay use of AzureCliCredential as it is only used in development
else:
return ManagedIdentityCredential(client_id=client_id)
From 1a95ca7b2432e9691b092f173f0c15c8b37ce174 Mon Sep 17 00:00:00 2001
From: Ajit Padhi
Date: Thu, 5 Feb 2026 15:44:11 +0530
Subject: [PATCH 32/38] vscode web url issue fix
---
documents/DeploymentGuide.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/documents/DeploymentGuide.md b/documents/DeploymentGuide.md
index ea9525ec..1d9fbd1f 100644
--- a/documents/DeploymentGuide.md
+++ b/documents/DeploymentGuide.md
@@ -119,7 +119,7 @@ Select one of the following options to deploy the Customer Chatbot Solution Acce
Option C: Visual Studio Code Web
- [&message=Open&color=blue&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/azure/?vscode-azure-exp=foundry&agentPayload=eyJiYXNlVXJsIjogImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9taWNyb3NvZnQvY3VzdG9tZXItY2hhdGJvdC1zb2x1dGlvbi1hY2NlbGVyYXRvci9yZWZzL2hlYWRzL21haW4vaW5mcmEvdnNjb2RlX3dlYiIsICJpbmRleFVybCI6ICIvaW5kZXguanNvbiIsICJ2YXJpYWJsZXMiOiB7ImFnZW50SWQiOiAiIiwgImNvbm5lY3Rpb25TdHJpbmciOiAiIiwgInRocmVhZElkIjogIiIsICJ1c2VyTWVzc2FnZSI6ICIiLCAicGxheWdyb3VuZE5hbWUiOiAiIiwgImxvY2F0aW9uIjogIiIsICJzdWJzY3JpcHRpb25JZCI6ICIiLCAicmVzb3VyY2VJZCI6ICIiLCAicHJvamVjdFJlc291cmNlSWQiOiAiIiwgImVuZHBvaW50IjogIiJ9LCAiY29kZVJvdXRlIjogWyJhaS1wcm9qZWN0cy1zZGsiLCAicHl0aG9uIiwgImRlZmF1bHQtYXp1cmUtYXV0aCIsICJlbmRwb2ludCJdfQ==)
+ [&message=Open&color=blue&logo=visualstudiocode&logoColor=white)](https://vscode.dev/azure/?vscode-azure-exp=foundry&agentPayload=eyJiYXNlVXJsIjogImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9taWNyb3NvZnQvY3VzdG9tZXItY2hhdGJvdC1zb2x1dGlvbi1hY2NlbGVyYXRvci9yZWZzL2hlYWRzL21haW4vaW5mcmEvdnNjb2RlX3dlYiIsICJpbmRleFVybCI6ICIvaW5kZXguanNvbiIsICJ2YXJpYWJsZXMiOiB7ImFnZW50SWQiOiAiIiwgImNvbm5lY3Rpb25TdHJpbmciOiAiIiwgInRocmVhZElkIjogIiIsICJ1c2VyTWVzc2FnZSI6ICIiLCAicGxheWdyb3VuZE5hbWUiOiAiIiwgImxvY2F0aW9uIjogIiIsICJzdWJzY3JpcHRpb25JZCI6ICIiLCAicmVzb3VyY2VJZCI6ICIiLCAicHJvamVjdFJlc291cmNlSWQiOiAiIiwgImVuZHBvaW50IjogIiJ9LCAiY29kZVJvdXRlIjogWyJhaS1wcm9qZWN0cy1zZGsiLCAicHl0aG9uIiwgImRlZmF1bHQtYXp1cmUtYXV0aCIsICJjb25uZWN0aW9uU3RyaW5nIl19)
1. Click the badge above (may take a few minutes to load)
2. Sign in with your Azure account when prompted
From dc7a7b956a9a28bbb33811913f69b3c1d743ca69 Mon Sep 17 00:00:00 2001
From: Ajit Padhi
Date: Thu, 5 Feb 2026 17:04:02 +0530
Subject: [PATCH 33/38] authentication issue fix
---
infra/scripts/data_scripts/azure_credential_utils.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/infra/scripts/data_scripts/azure_credential_utils.py b/infra/scripts/data_scripts/azure_credential_utils.py
index 64fd1f4d..db584d4a 100644
--- a/infra/scripts/data_scripts/azure_credential_utils.py
+++ b/infra/scripts/data_scripts/azure_credential_utils.py
@@ -1,4 +1,4 @@
-from azure.identity import DefaultAzureCredential, ManagedIdentityCredential
+from azure.identity import AzureCliCredential, ManagedIdentityCredential
APP_ENV = "dev" # Change to 'dev' for local development
@@ -14,12 +14,12 @@ def get_azure_credential(client_id=None):
client_id (str, optional): The client ID for the managed identity. Defaults to None.
Returns:
- azure.identity.DefaultAzureCredential or azure.identity.ManagedIdentityCredential:
+ azure.identity.AzureCliCredential or azure.identity.ManagedIdentityCredential:
The Azure credential object.
"""
if APP_ENV == "dev":
return (
- DefaultAzureCredential()
- ) # CodeQL [SM05139] Okay use of DefaultAzureCredential as it is only used in development
+ AzureCliCredential()
+ ) # CodeQL [SM05139] Okay use of AzureCliCredential as it is only used in development
else:
return ManagedIdentityCredential(client_id=client_id)
From 5684c3a2f70d406ac083d7fcbcbccfceae46a98b Mon Sep 17 00:00:00 2001
From: "Niraj Chaudhari (Persistent Systems Inc)"
Date: Mon, 9 Feb 2026 15:58:55 +0530
Subject: [PATCH 34/38] Update Troubleshoot doc
---
documents/TroubleShootingSteps.md | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/documents/TroubleShootingSteps.md b/documents/TroubleShootingSteps.md
index 9d5e7d3c..7c4f6815 100644
--- a/documents/TroubleShootingSteps.md
+++ b/documents/TroubleShootingSteps.md
@@ -58,6 +58,7 @@ Use these as quick reference guides to unblock your deployments.
| Issue/Error Code | Description | Steps to Resolve |
|-----------------|-------------|------------------|
| **InternalSubscriptionIsOverQuotaForSku/
ManagedEnvironmentProvisioningError** | Subscription quota exceeded for the requested SKU | Quotas are applied per resource group, subscriptions, accounts, and other scopes. For example, your subscription might be configured to limit the number of vCPUs for a region. If you attempt to deploy a virtual machine with more vCPUs than the permitted amount, you receive an error that the quota was exceeded.
For PowerShell, use the `Get-AzVMUsage` cmdlet to find virtual machine quotas:
`Get-AzVMUsage -Location "West US"`
Based on available quota you can deploy application otherwise, you can request for more quota |
+| **ServiceQuotaExceeded** | Free tier service quota limit reached for Azure AI Search | This error occurs when you attempt to deploy an Azure AI Search service but have already reached the **free tier quota limit** for your subscription. Each Azure subscription is limited to **one free tier Search service**.
**Example error message:**
`ServiceQuotaExceeded: Operation would exceed 'free' tier service quota. You are using 1 out of 1 'free' tier service quota.`
**Common causes:**
- Already have a free tier Azure AI Search service in the subscription
- Previous deployment created a free tier Search service that wasn't deleted
- Attempting to deploy multiple environments with free tier Search services
**Resolution:**
- **Option 1: Delete existing free tier Search service:**
`az search service list --query "[?sku.name=='free']" -o table`
`az search service delete --name --resource-group --yes` - **Option 2: Upgrade to a paid SKU:**
Modify your Bicep/ARM template to use `basic`, `standard`, or higher SKU instead of `free` - **Option 3: Use existing Search service:**
Reference the existing free tier Search service in your deployment instead of creating a new one - **Request quota increase:**
Submit a support request with issue type 'Service and subscription limits (quota)' and quota type 'Search' via [Azure Quota Request](https://aka.ms/AddQuotaSubscription)
**Reference:**
- [Azure AI Search service limits](https://learn.microsoft.com/en-us/azure/search/search-limits-quotas-capacity)
- [Azure AI Search pricing tiers](https://learn.microsoft.com/en-us/azure/search/search-sku-tier)
|
| **InsufficientQuota** | Not enough quota available in subscription | - Check if you have sufficient quota available in your subscription before deployment
- To verify, refer to the [quota_check](./QuotaCheck.md) file for details
|
| **MaxNumberOfRegionalEnvironmentsInSubExceeded** | Maximum Container App Environments limit reached for region |This error occurs when you attempt to create more **Azure Container App Environments** than the regional quota limit allows for your subscription. Each Azure region has a specific limit on the number of Container App Environments that can be created per subscription.
**Common Causes:**
- Deploying to regions with low quota limits (e.g., Sweden Central allows only 1 environment)
- Multiple deployments without cleaning up previous environments
- Exceeding the standard limit of 15 environments in most major regions
**Resolution:**
- **Delete unused environments** in the target region, OR
- **Deploy to a different region** with available capacity, OR
- **Request quota increase** via [Azure Support](https://go.microsoft.com/fwlink/?linkid=2208872)
**Reference:**
- [Azure Container Apps quotas](https://learn.microsoft.com/en-us/azure/container-apps/quotas)
- [Azure subscription and service limits](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits)
|
| **SkuNotAvailable** | Requested SKU not available in selected location or zone | You receive this error in the following scenarios:
- When the resource SKU you've selected, such as VM size, isn't available for a location or zone
- If you're deploying an Azure Spot VM or Spot scale set instance, and there isn't any capacity for Azure Spot in this location. For more information, see Spot error messages
|
@@ -88,7 +89,7 @@ Use these as quick reference guides to unblock your deployments.
| **ServiceUnavailable/ResourceNotFound** | Service unavailable or restricted in selected region | - Regions are restricted to guarantee compatibility with paired regions and replica locations for data redundancy and failover scenarios based on articles [Azure regions list](https://learn.microsoft.com/en-us/azure/reliability/regions-list) and [Azure Database for MySQL Flexible Server - Azure Regions](https://learn.microsoft.com/azure/mysql/flexible-server/overview#azure-regions)
- You can request more quota, refer [Quota Request](https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/create-support-request-quota-increase) Documentation
|
| **ResourceOperationFailure/
ProvisioningDisabled** | Resource provisioning restricted or disabled in region | - This error occurs when provisioning of a resource is restricted in the selected region. It usually happens because the service is not available in that region or provisioning has been temporarily disabled
- Regions are restricted to guarantee compatibility with paired regions and replica locations for data redundancy and failover scenarios based on articles [Azure regions list](https://learn.microsoft.com/en-us/azure/reliability/regions-list) and [Azure Database for MySQL Flexible Server - Azure Regions](https://learn.microsoft.com/azure/mysql/flexible-server/overview#azure-regions)
- If you need to use the same region, you can request a quota or provisioning exception. Refer [Quota Request](https://docs.microsoft.com/en-us/azure/sql-database/quota-increase-request) for more details
|
| **RedundancyConfigurationNotAvailableInRegion** | Redundancy configuration not supported in selected region | - This issue happens when you try to create a **Storage Account** with a redundancy configuration (e.g., `Standard_GRS`) that is **not supported in the selected Azure region**
- Example: Creating a storage account with **GRS** in **italynorth** will fail with error:
`az storage account create -n mystorageacct123 -g myResourceGroup -l italynorth --sku Standard_GRS --kind StorageV2` - To check supported SKUs for your region:
`az storage account list-skus -l italynorth -o table` - Use a supported redundancy option (e.g., Standard_LRS) in the same region or deploy the Storage Account in a region that supports your chosen redundancy
- For more details, refer to [Azure Storage redundancy documentation](https://learn.microsoft.com/en-us/azure/storage/common/storage-redundancy?utm_source=chatgpt.com)
|
-
+| **NoRegisteredProviderFound** | Unsupported API version for resource type in specified location | This error occurs when you attempt to deploy an Azure resource using an **API version that is not supported** for the specified resource type and location.
**Example error message:**
`NoRegisteredProviderFound: No registered resource provider found for location 'westeurope' and API version '2020-06-30' for type 'searchServices'. The supported api-versions are '2014-07-31-Preview, 2015-02-28, 2015-08-19, 2019-10-01-Preview, 2020-03-13, 2020-08-01, 2020-08-01-Preview, 2021-04-01-Preview, 2021-06-06-Preview, 2022-09-01, 2023-11-01, 2024-03-01-Preview, 2024-06-01-Preview, 2025-02-01-Preview, 2025-05-01'.`
**Common causes:**
- Using an outdated or invalid API version in Bicep/ARM templates
- Referencing an Azure Verified Module (AVM) that uses a deprecated API version
- Copy-pasting old template code with legacy API versions
- The API version was never valid (typo or incorrect version number)
**Resolution:**
- **Update the API version** in your Bicep/ARM template to a supported version listed in the error message. For example, change:
`resource searchService 'Microsoft.Search/searchServices@2020-06-30'`
to:
`resource searchService 'Microsoft.Search/searchServices@2025-05-01'` - **Check supported API versions** for a resource type:
`az provider show --namespace Microsoft.Search --query "resourceTypes[?resourceType=='searchServices'].apiVersions" -o table` - **Use the latest stable API version** when possible (avoid preview versions for production)
- **Update Azure Verified Modules (AVM)** to their latest versions if using external modules
- **Validate your template** before deployment:
`az deployment group validate --resource-group --template-file main.bicep`
**Reference:**
- [Azure Resource Manager API versions](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-providers-and-types)
- [Azure AI Search REST API versions](https://learn.microsoft.com/en-us/azure/search/search-api-versions)
|
--------------------------------
## Resource Naming & Validation
@@ -119,6 +120,9 @@ Use these as quick reference guides to unblock your deployments.
|-----------------|-------------|------------------|
| **NetcfgSubnetRangeOutsideVnet** | Subnet IP range outside virtual network address space | - Ensure the subnet's IP address range falls within the virtual network's address space
- Always validate that the subnet CIDR block is a subset of the VNet range
- For Azure Bastion, the AzureBastionSubnet must be at least /27
- Confirm that the AzureBastionSubnet is deployed inside the VNet
|
| **DisableExport_PublicNetworkAccessMustBeDisabled** | Public network access must be disabled when export is disabled | - **Check container source:** Confirm whether the deployment is using a Docker image or Azure Container Registry (ACR)
- **Verify ACR configuration:** If ACR is included, review its settings to ensure they comply with Azure requirements
- **Check export settings:** If export is disabled in ACR, make sure public network access is also disabled
- **Redeploy after fix:** Correct the configuration and redeploy. This will prevent the Conflict error during deployment
- For more information refer [ACR Data Loss Prevention](https://learn.microsoft.com/en-us/azure/container-registry/data-loss-prevention) document
|
+| **VMSizeIsNotPermittedToEnableAcceleratedNetworking** | VM size does not support accelerated networking | This error occurs when you attempt to enable accelerated networking on a VM size that does not support it.
**How to reproduce:**
- Create or deploy a VM (e.g., via ARM/Bicep) with size `Standard_A2m_v2`
- In the network interface configuration, set `"enableAcceleratedNetworking": true`
- Submit the request โ Azure throws `VMSizeIsNotPermittedToEnableAcceleratedNetworking`
**Resolution:**
- Use a supported VM size that supports accelerated networking
- Check the [Microsoft list of supported VM sizes for accelerated networking](https://learn.microsoft.com/en-us/azure/virtual-network/accelerated-networking-overview#supported-vm-instances)
- Alternatively, disable accelerated networking if the feature is not required for your workload
|
+**NetworkSecurityGroupNotCompliantForAzureBastionSubnet** / **SecurityRuleParameterContainsUnsupportedValue** | NSG rules blocking required Azure Bastion ports | This error occurs when the Network Security Group (NSG) attached to `AzureBastionSubnet` explicitly denies inbound TCP ports 443 and/or 4443, which Azure Bastion requires for management and tunneling.
**How to reproduce:**
- Deploy the template with `enablePrivateNetworking=true` so the virtualNetwork module creates `AzureBastionSubnet` and a Network Security Group that denies ports 443 and 4443
- Attempt to deploy Azure Bastion into that subnet
- During validation, Bastion detects the deny rules and fails with `NetworkSecurityGroupNotCompliantForAzureBastionSubnet`
**Resolution:**
- Allow inbound TCP 443 and 4443 on `AzureBastionSubnet` by updating or removing the NSG deny rules
- Alternatively, deploy Bastion to a subnet without restrictive NSG rules
- For more details, refer to [Azure Bastion NSG requirements](https://learn.microsoft.com/en-us/azure/bastion/bastion-nsg)
|
+| **RouteTableCannotBeAttachedForAzureBastionSubnet** | Route table attached to Azure Bastion subnet | This error occurs because Azure Bastion subnet (`AzureBastionSubnet`) has a platform restriction that prevents route tables from being attached.
**How to reproduce:**
- In `virtualNetwork.bicep`, add `attachRouteTable: true` to the `AzureBastionSubnet` configuration:
`{ name: 'AzureBastionSubnet', addressPrefixes: ['10.0.10.0/26'], attachRouteTable: true }` - Add a Route Table module to the template
- Update subnet creation to attach route table conditionally:
`routeTableResourceId: subnet.?attachRouteTable == true ? routeTable.outputs.resourceId : null` - Deploy the template โ Azure throws `RouteTableCannotBeAttachedForAzureBastionSubnet`
**Resolution:**
- Remove the `attachRouteTable: true` flag from `AzureBastionSubnet` configuration
- Ensure no route table is associated with `AzureBastionSubnet`
- Route tables can only be attached to other subnets, not `AzureBastionSubnet`
- For more details, refer to [Azure Bastion subnet requirements](https://learn.microsoft.com/en-us/azure/bastion/configuration-settings#subnet)
|
---------------------------------
@@ -129,6 +133,9 @@ Use these as quick reference guides to unblock your deployments.
| **InvalidRequestContent** | Deployment contains unrecognized or missing required values | - The deployment values either include values that aren't recognized, or required values are missing. Confirm the values for your resource type
- You can refer [Invalid Request Content error](https://learn.microsoft.com/en-us/azure/azure-resource-manager/troubleshooting/common-deployment-errors#:~:text=InvalidRequestContent,Template%20reference) documentation
|
| **Conflict - Cannot use the SKU Basic with File Change Audit for site** | File Change Audit not supported on Basic SKU | - This error happens because File Change Audit logs aren't supported on Basic SKU App Service Plans
- Upgrading to Premium/Isolated SKU (supports File Change Audit), or
- Disabling File Change Audit in Diagnostic Settings if you must stay on Basic
- Always cross-check the [supported log types](https://aka.ms/supported-log-types) before adding diagnostic logs to your Bicep templates
|
| **AccountPropertyCannotBeUpdated** | Read-only property cannot be modified after creation | The property **`isHnsEnabled`** (Hierarchical Namespace for Data Lake Gen2) is **read-only** and can only be set during **storage account creation**. Once a storage account is created, this property **cannot be updated**. Trying to update it via ARM template, Bicep, CLI, or Portal will fail.
**Resolution:**
- Create a **new storage account** with `isHnsEnabled=true` if you require hierarchical namespace
- Migration may be needed if you already have data
- Refer to [Storage Account Update Restrictions](https://aka.ms/storageaccountupdate) for more details
|
+| **Conflict - Local authentication is disabled** | App Configuration store has local authentication disabled but application is using local auth mode | This error occurs when your Azure App Configuration store has **local authentication disabled** (`disableLocalAuth: true`) but your application is trying to access it using **connection strings or access keys** instead of **Azure AD/Managed Identity authentication**.
**Example error message:**
`The operation cannot be performed because the configuration store is using local authentication mode and local authentication is disabled. To enable access to data plane resources while local authentication is disabled, please use pass-through authentication mode.`
**Common causes:**
- App Configuration store deployed with `disableLocalAuth: true` for security compliance
- Application code using connection strings instead of Managed Identity
- SDK client initialized with access keys rather than `DefaultAzureCredential`
**Resolution:**
- **Option 1: Update application to use Managed Identity (Recommended)**
```python
from azure.identity import DefaultAzureCredential
from azure.appconfiguration import AzureAppConfigurationClient
credential = DefaultAzureCredential()
client = AzureAppConfigurationClient(
endpoint="https://your-appconfig.azconfig.io",
credential=credential
)
``` - **Option 2: Re-enable local authentication (Not recommended for production)**
Set `disableLocalAuth: false` in your Bicep/ARM template - **Ensure proper RBAC assignment:** Verify that the Managed Identity has `App Configuration Data Reader` or `App Configuration Data Owner` role assigned
**Reference:**
- [Disable local authentication in Azure App Configuration](https://learn.microsoft.com/en-us/azure/azure-app-configuration/howto-disable-access-key-authentication)
- [Use Managed Identities to access App Configuration](https://learn.microsoft.com/en-us/azure/azure-app-configuration/howto-integrate-azure-managed-service-identity)
|
+| **PropertyChangeNotAllowed** | Immutable VM property cannot be changed after creation | This error occurs when you attempt to modify an immutable property (such as `osProfile.adminUsername`) on an existing VM.
**Cause (Azure Limitation):**
Once a VM is created, the `osProfile.adminUsername` property is immutable and cannot be changed. If you modify the VM username or password in the template and redeploy, this issue will occur.
**Resolution:**
- Delete the existing deployment and redeploy with new credentials:
`azd down --force --purge` - Set new credentials before redeployment:
`azd env set AZURE_ENV_VM_ADMIN_USERNAME "newusername"`
`azd env set AZURE_ENV_VM_ADMIN_PASSWORD "NewSecurePassword123!"` - Redeploy:
`azd up`
**Note:** Some VM properties are immutable by design. Always plan credential and configuration changes before initial deployment. |
+
----------------------------------
@@ -140,7 +147,8 @@ Use these as quick reference guides to unblock your deployments.
| **AccountProvisioningStateInvalid** | Resource used before provisioning completed | - The AccountProvisioningStateInvalid error occurs when you try to use resources while they are still in the Accepted provisioning state
- This means the deployment has not yet fully completed
- To avoid this error, wait until the provisioning state changes to Succeeded
- Only use the resources once the deployment is fully completed
|
| **BadRequest - DatabaseAccount is in a failed provisioning state because the previous attempt to create it was not successful** | Database account failed to provision previously | - This error occurs when a user attempts to redeploy a resource that previously failed to provision
- To resolve the issue, delete the failed deployment first, then start a new deployment
- For guidance on deleting a resource from a Resource Group, refer to the following link: [Delete an Azure Cosmos DB account](https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/manage-with-powershell#delete-account:~:text=%3A%24enableMultiMaster-,Delete%20an%20Azure%20Cosmos%20DB%20account,-This%20command%20deletes)
|
| **ServiceDeleting** | Cannot provision service because deletion is still in progress | This error occurs when you attempt to create an Azure Search service with the same name as one that is currently being deleted. Azure Search services have a **soft-delete period** during which the service name remains reserved.
**Common causes:**
- Deleting a Search service and immediately trying to recreate it with the same name
- Rapid redeployments using the same service name in Bicep/ARM templates
- The deletion operation is asynchronous and takes several minutes to complete
**Resolution:**
- **Wait for deletion to complete** (10-15 minutes) before redeploying
- **Use a different service name** - append timestamp or unique identifier to the name
- **Implement retry logic** with exponential backoff as suggested in the error message
- **Check deletion status** before recreating:
`az search service show --name --resource-group ` - For Bicep deployments, ensure your naming strategy includes unique suffixes to avoid conflicts
- For more details, refer to [Azure Search service limits](https://learn.microsoft.com/en-us/azure/search/search-limits-quotas-capacity)
|
-
+| **FailedIdentityOperation / ManagedEnvironmentScheduledForDelete** | Identity operation failed due to pending delete or resource conflict | This error occurs when you attempt to create or update an Azure Container Apps Managed Environment while it has a **pending delete operation** or the resource already exists in a conflicting state.
**Example error messages:**
`FailedIdentityOperation: Identity operation for resource failed with error 'Failed to perform resource identity operation. Status: 'Conflict'. Response: 'Request specified that resource is new, but resource already exists. This may be due to a pending delete operation, try again later.'`
`ManagedEnvironmentScheduledForDelete: The environment 'cae-xxx' is under deletion. Please retry the creation with new name or wait for the deletion completed.`
**Common causes:**
- Deleting a Container Apps Environment and immediately trying to recreate it with the same name
- Rapid redeployments using `azd up` without waiting for previous cleanup
- Resource group deletion in progress while attempting to redeploy
- Previous deployment failed or was canceled, leaving resources in an inconsistent state
- Concurrent deployments targeting the same resources
**Resolution:**
- **Wait for deletion to complete** (5-15 minutes) before redeploying:
`az containerapp env show --name --resource-group --query "properties.provisioningState"` - **Check environment status:** If status is `ScheduledForDelete` or `Deleting`, wait for it to complete
- **Use a new environment name:** Create a new environment with a different name or use a new resource group:
`azd env new `
`azd up` - **Force delete and wait:** If the environment is stuck, try force deletion:
`az containerapp env delete --name --resource-group --yes`
Wait for deletion to complete before redeploying - **Delete associated Container Apps first:** If the environment has apps, delete them before the environment:
`az containerapp list --environment --resource-group -o table`
`az containerapp delete --name --resource-group --yes` - **Use unique naming:** Implement timestamp or unique suffix in your naming strategy to avoid conflicts
**Reference:**
- [Azure Container Apps troubleshooting](https://learn.microsoft.com/en-us/azure/container-apps/troubleshooting)
- [Manage Container Apps environments](https://learn.microsoft.com/en-us/azure/container-apps/environment)
|
+| **BadRequest - Parent account does not provision correctly** | Parent AI Services/Cognitive Services account failed to provision | This error occurs when a **child resource** (such as an AI project, model deployment, or other dependent resource) attempts to be created on a **parent Cognitive Services/AI Services account** that has **failed to provision** or is in an incomplete state.
**Example error message:**
`Parent account does not provision correctly, please retry creating the account.`
**Common causes:**
- Parent AI Services account provisioning failed due to quota, region, or configuration issues
- Using `restore: true` flag when no soft-deleted resource exists to restore
- Network or transient errors during parent account creation
- Invalid configuration on the parent account (e.g., invalid SKU, unsupported region)
- Previous deployment of the parent account was interrupted or canceled
**Resolution:**
- **Check parent account status:**
`az cognitiveservices account show --name --resource-group --query "properties.provisioningState"` - **Delete failed parent account and redeploy:**
`az cognitiveservices account delete --name --resource-group `
Then run: `azd up` - **If using restore flag incorrectly:** Ensure `restore: false` in your Bicep template unless you specifically need to restore a soft-deleted resource
- **Check for soft-deleted resources:**
`az cognitiveservices account list-deleted` - **Purge soft-deleted resources if needed:**
`az cognitiveservices account purge --name --resource-group --location ` - **Verify quota and region availability:** Ensure you have sufficient quota and the service is available in your selected region
**Reference:**
- [Manage Cognitive Services accounts](https://learn.microsoft.com/en-us/azure/ai-services/manage-resources)
- [Recover deleted Cognitive Services resources](https://learn.microsoft.com/en-us/azure/ai-services/recover-purge-resources)
|
---------------------------------
## Miscellaneous
@@ -150,8 +158,10 @@ Use these as quick reference guides to unblock your deployments.
| **DeploymentModelNotSupported/
ServiceModelDeprecated/
InvalidResourceProperties** | Model not supported or deprecated in selected region | - The updated model may not be supported in the selected region. Please verify its availability in the [Azure AI Foundry models](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/concepts/models?tabs=global-standard%2Cstandard-chat-completions) document
|
| **FlagMustBeSetForRestore/
NameUnavailable/
CustomDomainInUse** | Soft-deleted resource requires restore flag or purge | This error occurs when you try to deploy a Cognitive Services resource that was **soft-deleted** earlier. Azure requires you to explicitly set the **`restore` flag** to `true` if you want to recover the soft-deleted resource. If you don't want to restore the resource, you must **purge the deleted resource** first before redeploying.
**Example causes:**
- Trying to redeploy a Cognitive Services account with the same name as a previously deleted one
- The deleted resource still exists in a **soft-delete retention state**
**How to fix:**
- If you want to restore โ add `"restore": true` in your template properties
- If you want a fresh deployment โ purge the resource using:
`az cognitiveservices account purge --name --resource-group --location ` - For more details, refer to [Soft delete and resource restore](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/delete-resource-group?tabs=azure-powershell)
|
| **ContainerAppOperationError** | Container image build or deployment issue | - The error is likely due to an improperly built container image. For resolution steps, refer to the [Azure Container Registry (ACR) โ Build & Push Guide](./ACRBuildAndPushGuide.md)
|
+| **LinkedAuthorizationFailed** | Service principal lacks permission to use a linked resource required for deployment | This error occurs when a service principal doesn't have permission to perform an action on a linked resource that is required for the operation (e.g., cluster creation).
**Common causes:**
- The service principal has permission on the primary resource but lacks permission on the linked scope
- Missing role assignment for operations like `Microsoft.Network/ddosProtectionPlans/join/action`
**Resolution:**
- Identify the **service principal**, **resource**, and **operation** from the error message
- Grant the service principal the required permissions on the linked resource
- Use [Assign Azure roles using the Azure portal](https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal) to add the role assignment
- For more details, refer to [LinkedAuthorizationFailed error](https://learn.microsoft.com/en-us/troubleshoot/azure/azure-kubernetes/error-codes/linkedauthorizationfailed-error)
|
+| **ContainerOperationFailure** | Container image or storage resource does not exist | This error occurs when an operation fails because the **specified container resource does not exist**. This can happen with Azure Container Registry images or Azure Storage blob containers.
**Example error message:**
`ContainerOperationFailure: The specified resource does not exist. RequestId:xxxxx Time:xxxxx`
**Common causes:**
- **Invalid container image tag:** The specified image tag does not exist in the container registry
- **Non-existent container registry:** The container registry endpoint is incorrect or inaccessible
- **Missing blob container:** The storage blob container referenced by the application does not exist
- **Incorrect storage account URL:** The storage account endpoint is misconfigured
- **Permission issues:** The managed identity lacks permissions to access the container registry or storage account
**Resolution:**
- **Verify container image exists:**
`az acr repository show-tags --name --repository ` - **Check image tag in deployment:** Ensure the `imageTag` parameter matches an existing tag in the registry
- **Verify storage containers exist:**
`az storage container list --account-name --auth-mode login` - **Check role assignments:** Ensure the Container App's managed identity has `AcrPull` role on the container registry and `Storage Blob Data Contributor` role on the storage account
- **Rebuild and push container images:** If images are missing, follow the [ACR Build & Push Guide](./ACRBuildAndPushGuide.md)
- **Verify storage account URL:** Ensure `APP_STORAGE_BLOB_URL` and `APP_STORAGE_QUEUE_URL` in App Configuration point to the correct storage account
**Reference:**
- [Azure Container Registry troubleshooting](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-troubleshoot-login)
- [Azure Storage troubleshooting](https://learn.microsoft.com/en-us/azure/storage/common/storage-troubleshoot-common-errors)
|
---------------------------------
๐ก Note: If you encounter any other issues, you can refer to the [Common Deployment Errors](https://learn.microsoft.com/en-us/azure/azure-resource-manager/troubleshooting/common-deployment-errors) documentation.
-If the problem persists, you can also raise an bug in our [Document Generation Github Issues](https://github.com/microsoft/document-generation-solution-accelerator/issues) for further support.
\ No newline at end of file
+If the problem persists, you can also raise an bug in our [Customer Chatbot Github Issues](https://github.com/microsoft/customer-chatbot-solution-accelerator/issues) for further support.
\ No newline at end of file
From 4998f89612679750aef70c2bc7e737f1f02b71b4 Mon Sep 17 00:00:00 2001
From: Harmanpreet-Microsoft
Date: Mon, 16 Feb 2026 20:26:41 +0530
Subject: [PATCH 35/38] Enhance Cosmos DB firewall configuration to support
multiple IPs for NAT/proxy variations
---
.../data_scripts/run_upload_data_scripts.sh | 28 ++++++++++++++++---
1 file changed, 24 insertions(+), 4 deletions(-)
diff --git a/infra/scripts/data_scripts/run_upload_data_scripts.sh b/infra/scripts/data_scripts/run_upload_data_scripts.sh
index be89c0f1..c5223364 100644
--- a/infra/scripts/data_scripts/run_upload_data_scripts.sh
+++ b/infra/scripts/data_scripts/run_upload_data_scripts.sh
@@ -162,15 +162,35 @@ enable_public_access() {
echo "Cosmos DB public access is '$original_cosmos_public_access' - enabling access"
- # Add current IP to firewall rules and enable public network access
- echo "Adding current IP ($current_ip) to Cosmos DB firewall..."
+ # Build array of individual IPs to handle NAT/proxy variations (current IP ยฑ2)
+ IFS='.' read -r ip1 ip2 ip3 ip4 <<< "$current_ip"
+
+ echo "Adding multiple IPs to Cosmos DB firewall to handle NAT/proxy variations..."
+ echo " Base IP: $current_ip"
+
+ # Build JSON array with current IP and ยฑ2 range
+ ip_rules="["
+ for offset in -2 -1 0 1 2; do
+ new_octet=$((ip4 + offset))
+ if [ $new_octet -ge 0 ] && [ $new_octet -le 255 ]; then
+ if [ "$ip_rules" != "[" ]; then
+ ip_rules="${ip_rules},"
+ fi
+ ip_rules="${ip_rules}{\"ipAddressOrRange\":\"${ip1}.${ip2}.${ip3}.${new_octet}\"}"
+ fi
+ done
+ ip_rules="${ip_rules}]"
+
+ echo " Adding 5 IPs: ${ip1}.${ip2}.${ip3}.$((ip4-2)) to ${ip1}.${ip2}.${ip3}.$((ip4+2))"
+
+ # Add multiple IPs to firewall rules and enable public network access
if MSYS_NO_PATHCONV=1 az resource update \
--ids "$cosmos_resource_id" \
--api-version 2021-04-15 \
- --set "properties.ipRules=[{\"ipAddressOrRange\":\"$current_ip\"}]" \
+ --set "properties.ipRules=$ip_rules" \
--set "properties.publicNetworkAccess=Enabled" \
--output none; then
- echo "โ Cosmos DB firewall updated to allow current IP"
+ echo "โ Cosmos DB firewall updated with multiple IPs for NAT handling"
echo "โ Cosmos DB public network access enabled"
# Wait longer for changes to propagate
From c2fe7b288fd861dbc7aefe2619de75bd44d159c5 Mon Sep 17 00:00:00 2001
From: Ajit Padhi
Date: Tue, 17 Feb 2026 16:09:01 +0530
Subject: [PATCH 36/38] Readme update for vs code web link
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 43797973..12ce87e1 100644
--- a/README.md
+++ b/README.md
@@ -67,8 +67,8 @@ Follow the quick deploy steps on the deployment guide to deploy this solution to
[Click here to launch the deployment guide](./documents/DeploymentGuide.md)
-| [](https://codespaces.new/microsoft/customer-chatbot-solution-accelerator) | [](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/customer-chatbot-solution-accelerator) |
-|---|---|
+| [](https://codespaces.new/microsoft/customer-chatbot-solution-accelerator) | [](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/customer-chatbot-solution-accelerator) | [&message=Open&color=blue&logo=visualstudiocode&logoColor=white)](https://vscode.dev/azure/?vscode-azure-exp=foundry&agentPayload=eyJiYXNlVXJsIjogImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9taWNyb3NvZnQvY3VzdG9tZXItY2hhdGJvdC1zb2x1dGlvbi1hY2NlbGVyYXRvci9yZWZzL2hlYWRzL21haW4vaW5mcmEvdnNjb2RlX3dlYiIsICJpbmRleFVybCI6ICIvaW5kZXguanNvbiIsICJ2YXJpYWJsZXMiOiB7ImFnZW50SWQiOiAiIiwgImNvbm5lY3Rpb25TdHJpbmciOiAiIiwgInRocmVhZElkIjogIiIsICJ1c2VyTWVzc2FnZSI6ICIiLCAicGxheWdyb3VuZE5hbWUiOiAiIiwgImxvY2F0aW9uIjogIiIsICJzdWJzY3JpcHRpb25JZCI6ICIiLCAicmVzb3VyY2VJZCI6ICIiLCAicHJvamVjdFJlc291cmNlSWQiOiAiIiwgImVuZHBvaW50IjogIiJ9LCAiY29kZVJvdXRlIjogWyJhaS1wcm9qZWN0cy1zZGsiLCAicHl0aG9uIiwgImRlZmF1bHQtYXp1cmUtYXV0aCIsICJjb25uZWN0aW9uU3RyaW5nIl19)
+|---|---|---|
> โ ๏ธ **Important: Check Azure OpenAI Quota Availability**
> To ensure sufficient quota is available in your subscription, please follow [quota check instructions guide](./documents/QuotaCheck.md) before you deploy the solution.
From c4a67ecf796a4ce566028ba0d47d7bfffecfc7b4 Mon Sep 17 00:00:00 2001
From: Prajwal-Microsoft
Date: Thu, 19 Feb 2026 13:35:08 +0530
Subject: [PATCH 37/38] docs: Update README with new solution accelerators and
playbooks
Added new solution accelerators and playbooks for AI and data engineering.
---
README.md | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/README.md b/README.md
index 12ce87e1..a127ce7c 100644
--- a/README.md
+++ b/README.md
@@ -166,6 +166,19 @@ Check out similar solution accelerators
| Solution Accelerator | Description |
|---|---|
| [Agentic Applications for Unified Data Foundation](https://github.com/microsoft/agentic-applications-for-unified-data-foundation-solution-accelerator) | Empowers organizations to make faster, smarter decisions at scale by leveraging agentic AI solutions built on a unified data foundation with Microsoft Fabric. |
+|[GPT-RAG Accelerator](https://github.com/Azure/gpt-rag)| Secure enterprise GPT assistant framework that uses Retrieval-Augmented Generation to ground answers on your data. It provides a ready architecture (Azure OpenAI + knowledge search) for building AI chatbots that โknowโ your enterprise content, with built-in security and scalability.|
+|[Document Processing Accelerator](https://github.com/Azure/doc-proc-solution-accelerator/) | Modular document AI pipeline that automatically extracts, analyzes, and indexes information from unstructured documents (PDFs, images, etc.) at scale. It offers plug-and-play components for OCR, classification, summarization, and integration to search or chatbots โ speeding up data ingestion with enterprise security.|
+
+
+
+๐ก Want to get familiar with Microsoft's AI and Data Engineering best practices? Check out our playbooks to learn more
+
+| Playbook | Description |
+|:---|:---|
+| [AI playbook](https://learn.microsoft.com/en-us/ai/playbook/) | The Artificial Intelligence (AI) Playbook provides enterprise software engineers with solutions, capabilities, and code developed to solve real-world AI problems. |
+| [Data playbook](https://learn.microsoft.com/en-us/data-engineering/playbook/understanding-data-playbook) | The data playbook provides enterprise software engineers with solutions which contain code developed to solve real-world problems. Everything in the playbook is developed with, and validated by, some of Microsoft's largest and most influential customers and partners. |
+
+
## Provide feedback
From db29e7ab9bd8e1c4b1b8bee8071206082480e180 Mon Sep 17 00:00:00 2001
From: Ajit Padhi
Date: Mon, 23 Feb 2026 23:27:22 +0530
Subject: [PATCH 38/38] Post deployment issue fix for opentelemetry
---
infra/scripts/agent_scripts/requirements.txt | 7 ++++---
src/api/requirements.txt | 5 +++--
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/infra/scripts/agent_scripts/requirements.txt b/infra/scripts/agent_scripts/requirements.txt
index 63e0edcf..ebfb6d91 100644
--- a/infra/scripts/agent_scripts/requirements.txt
+++ b/infra/scripts/agent_scripts/requirements.txt
@@ -1,6 +1,7 @@
-agent-framework-azure-ai==1.0.0b260130
-agent-framework-core==1.0.0b260130
+agent-framework-azure-ai==1.0.0rc1
+agent-framework-core==1.0.0rc1
azure-ai-projects==2.0.0b3
azure-identity==1.25.1
python-dotenv==1.1.1
-aiohttp==3.13.3
\ No newline at end of file
+aiohttp==3.13.3
+opentelemetry-semantic-conventions-ai==0.4.13
\ No newline at end of file
diff --git a/src/api/requirements.txt b/src/api/requirements.txt
index ee6cfdcc..e6f71e4c 100644
--- a/src/api/requirements.txt
+++ b/src/api/requirements.txt
@@ -6,8 +6,8 @@ uvicorn[standard]==0.40.0
pydantic[email]==2.11.10
# Azure Services
-agent-framework-azure-ai==1.0.0b260212
-agent-framework-core==1.0.0b260212
+agent-framework-azure-ai==1.0.0rc1
+agent-framework-core==1.0.0rc1
azure-identity==1.25.2
azure-search-documents==11.7.0b1
azure-ai-projects==2.0.0b3
@@ -22,6 +22,7 @@ opentelemetry-exporter-otlp-proto-http
azure-monitor-events-extension
opentelemetry-sdk==1.39.0
opentelemetry-api==1.39.0
+opentelemetry-semantic-conventions-ai==0.4.13
opentelemetry-semantic-conventions==0.60b0
opentelemetry-instrumentation==0.60b0
azure-monitor-opentelemetry==1.8.6