From 0943c7eb8deaa64f8dd6d78aaf82610fbadeab61 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Sun, 22 Mar 2026 11:09:42 +0530 Subject: [PATCH 1/5] refactor notification email templates --- .github/workflows/deploy-orchestrator.yml | 35 ++- .github/workflows/job-send-notification.yml | 331 ++++++-------------- 2 files changed, 119 insertions(+), 247 deletions(-) diff --git a/.github/workflows/deploy-orchestrator.yml b/.github/workflows/deploy-orchestrator.yml index c3be5e9e..3f55241c 100644 --- a/.github/workflows/deploy-orchestrator.yml +++ b/.github/workflows/deploy-orchestrator.yml @@ -101,9 +101,25 @@ jobs: TEST_SUITE: ${{ inputs.trigger_type == 'workflow_dispatch' && inputs.run_e2e_tests || 'GoldenPath-Testing' }} secrets: inherit + cleanup-deployment: + if: "!cancelled() && 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 + send-notification: if: "!cancelled()" - needs: [docker-build, deploy, e2e-test] + needs: [docker-build, deploy, e2e-test, cleanup-deployment] uses: ./.github/workflows/job-send-notification.yml with: trigger_type: ${{ inputs.trigger_type }} @@ -113,25 +129,10 @@ jobs: existing_webapp_url: ${{ inputs.existing_webapp_url }} deploy_result: ${{ needs.deploy.result }} e2e_test_result: ${{ needs.e2e-test.result }} + cleanup_result: ${{ needs.cleanup-deployment.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.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/job-send-notification.yml b/.github/workflows/job-send-notification.yml index 14d50f15..87154f99 100644 --- a/.github/workflows/job-send-notification.yml +++ b/.github/workflows/job-send-notification.yml @@ -33,7 +33,8 @@ on: type: string e2e_test_result: description: 'E2E test job result (success, failure, skipped)' - required: true + required: false + default: '' type: string CONTAINER_WEB_APPURL: description: 'Container Web App URL' @@ -60,6 +61,11 @@ on: required: false default: '' type: string + cleanup_result: + description: 'Cleanup job result (success, failure, skipped)' + required: false + default: 'skipped' + type: string env: GPT_MIN_CAPACITY: 100 @@ -67,6 +73,7 @@ env: 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 @@ -74,162 +81,6 @@ jobs: env: accelerator_name: "Content Processing" 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', 'false', or empty string) - if [[ "$INPUT_QUOTA_FAILED" != "true" && "$INPUT_QUOTA_FAILED" != "false" && "$INPUT_QUOTA_FAILED" != "" ]]; then - echo "❌ ERROR: QUOTA_FAILED must be 'true', 'false', or empty string, 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 @@ -248,24 +99,45 @@ jobs: echo "TEST_SUITE_NAME=$TEST_SUITE_NAME" >> $GITHUB_OUTPUT echo "Test Suite: $TEST_SUITE_NAME" + - name: Determine Cleanup Status + id: cleanup + shell: bash + env: + CLEANUP_RESULT: ${{ inputs.cleanup_result }} + run: | + case "$CLEANUP_RESULT" in + success) echo "CLEANUP_STATUS=✅ SUCCESS" >> $GITHUB_OUTPUT ;; + failure) echo "CLEANUP_STATUS=❌ FAILED (Needs Manual Cleanup)" >> $GITHUB_OUTPUT ;; + *) echo "CLEANUP_STATUS=⏭️ SKIPPED (Needs Manual Cleanup)" >> $GITHUB_OUTPUT ;; + esac + + - name: Determine Configuration Label + id: config + shell: bash + env: + WAF_ENABLED: ${{ env.WAF_ENABLED }} + EXP: ${{ env.EXP }} + run: | + WAF_LABEL=$( [ "$WAF_ENABLED" = "true" ] && echo "WAF" || echo "Non-WAF" ) + EXP_LABEL=$( [ "$EXP" = "true" ] && echo "EXP" || echo "Non-EXP" ) + echo "CONFIG_LABEL=${WAF_LABEL} + ${EXP_LABEL}" >> $GITHUB_OUTPUT + - name: Send Quota Failure Notification if: inputs.deploy_result == 'failure' && inputs.QUOTA_FAILED == 'true' shell: bash env: - DEPLOY_RESULT: ${{ inputs.deploy_result }} - QUOTA_FAILED: ${{ inputs.QUOTA_FAILED }} - ACCELERATOR_NAME: ${{ env.accelerator_name }} - GPT_MIN_CAPACITY: ${{ env.GPT_MIN_CAPACITY }} - AZURE_REGIONS: ${{ vars.AZURE_REGIONS }} GITHUB_REPOSITORY: ${{ github.repository }} GITHUB_RUN_ID: ${{ github.run_id }} + ACCELERATOR_NAME: ${{ env.accelerator_name }} LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }} + CLEANUP_STATUS: ${{ steps.cleanup.outputs.CLEANUP_STATUS }} + CONFIG_LABEL: ${{ steps.config.outputs.CONFIG_LABEL }} 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: ${GPT_MIN_CAPACITY}
• Checked Regions: ${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)" + "body": "

Dear Team,

We would like to inform you that the ${ACCELERATOR_NAME} deployment has failed due to insufficient quota.

Status Summary:
StageStatus
Deployment❌ FAILED (Insufficient Quota)
E2E Tests⏭️ SKIPPED
Cleanup${CLEANUP_STATUS}

Configuration: ${CONFIG_LABEL}

Run URL: ${RUN_URL}

Please resolve the quota issue and retry the deployment.

Best regards,
Your Automation Team

", + "subject": "[CI/CD-Automation] [${ACCELERATOR_NAME}] Insufficient Quota" } EOF ) @@ -278,23 +150,19 @@ jobs: if: inputs.deploy_result == 'failure' && inputs.QUOTA_FAILED != 'true' shell: bash env: - DEPLOY_RESULT: ${{ inputs.deploy_result }} - QUOTA_FAILED: ${{ inputs.QUOTA_FAILED }} - RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }} + INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }} ACCELERATOR_NAME: ${{ env.accelerator_name }} - WAF_ENABLED: ${{ env.WAF_ENABLED }} - EXP: ${{ env.EXP }} - GITHUB_REPOSITORY: ${{ github.repository }} - GITHUB_RUN_ID: ${{ github.run_id }} LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }} + CONFIG_LABEL: ${{ steps.config.outputs.CONFIG_LABEL }} + CLEANUP_STATUS: ${{ steps.cleanup.outputs.CLEANUP_STATUS }} run: | - RUN_URL="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" - RESOURCE_GROUP="${RESOURCE_GROUP_NAME}" + 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" + "body": "

Dear Team,

We would like to inform you that the ${ACCELERATOR_NAME} deployment has failed.

Status Summary:
StageStatus
Deployment❌ FAILED (Deployment Issue)
E2E Tests⏭️ SKIPPED
Cleanup${CLEANUP_STATUS}

Deployment Details:
• Resource Group: ${RESOURCE_GROUP}

Configuration: ${CONFIG_LABEL}

Run URL: ${RUN_URL}

Please investigate the deployment failure at your earliest convenience.

Best regards,
Your Automation Team

", + "subject": "[CI/CD-Automation] [${ACCELERATOR_NAME}] Deployment-Failed" } EOF ) @@ -307,38 +175,39 @@ jobs: if: inputs.deploy_result == 'success' && (inputs.e2e_test_result == 'skipped' || inputs.TEST_SUCCESS == 'true') shell: bash env: - DEPLOY_RESULT: ${{ inputs.deploy_result }} - E2E_TEST_RESULT: ${{ inputs.e2e_test_result }} - TEST_SUCCESS: ${{ inputs.TEST_SUCCESS }} - CONTAINER_WEB_APPURL: ${{ inputs.CONTAINER_WEB_APPURL }} - EXISTING_WEBAPP_URL: ${{ inputs.existing_webapp_url }} - RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }} - TEST_REPORT_URL: ${{ inputs.TEST_REPORT_URL }} - TEST_SUITE_NAME: ${{ steps.test_suite.outputs.TEST_SUITE_NAME }} + INPUT_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 }} - WAF_ENABLED: ${{ env.WAF_ENABLED }} - EXP: ${{ env.EXP }} + LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }} GITHUB_REPOSITORY: ${{ github.repository }} GITHUB_RUN_ID: ${{ github.run_id }} - LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }} + CONFIG_LABEL: ${{ steps.config.outputs.CONFIG_LABEL }} + CLEANUP_STATUS: ${{ steps.cleanup.outputs.CLEANUP_STATUS }} + RUN_E2E_TESTS: ${{ env.RUN_E2E_TESTS }} + TEST_SUITE_NAME: ${{ steps.test_suite.outputs.TEST_SUITE_NAME }} + run: | RUN_URL="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" - WEBAPP_URL="${CONTAINER_WEB_APPURL:-${EXISTING_WEBAPP_URL}}" - RESOURCE_GROUP="${RESOURCE_GROUP_NAME}" + WEBAPP_URL="${INPUT_WEB_APPURL:-$INPUT_EXISTING_WEBAPP_URL}" + RESOURCE_GROUP="$INPUT_RESOURCE_GROUP_NAME" + TEST_REPORT_URL="$INPUT_TEST_REPORT_URL" - if [ "${E2E_TEST_RESULT}" = "skipped" ]; then + 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" + "body": "

Dear Team,

We would like to inform you that the ${ACCELERATOR_NAME} deployment has completed successfully.

Status Summary:
StageStatus
Deployment✅ SUCCESS
E2E Tests⏭️ SKIPPED
Cleanup${CLEANUP_STATUS}

Deployment Details:
• Resource Group: ${RESOURCE_GROUP}
• Web App URL: ${WEBAPP_URL}

Configuration: ${CONFIG_LABEL}

Run URL: ${RUN_URL}

Best regards,
Your Automation Team

", + "subject": "[CI/CD-Automation] [${ACCELERATOR_NAME}] 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" + "body": "

Dear Team,

We would like to inform you that the ${ACCELERATOR_NAME} deployment and test automation has completed successfully.

Status Summary:
StageStatus
Deployment✅ SUCCESS
E2E Tests✅ SUCCESS
Cleanup${CLEANUP_STATUS}

Deployment Details:
• Resource Group: ${RESOURCE_GROUP}
• Web App URL: ${WEBAPP_URL}
• Test Suite: ${TEST_SUITE_NAME}
• Test Report: View Report

Configuration: ${CONFIG_LABEL}

Run URL: ${RUN_URL}

Best regards,
Your Automation Team

", + "subject": "[CI/CD-Automation] [${ACCELERATOR_NAME}] Success" } EOF ) @@ -352,27 +221,28 @@ jobs: if: inputs.deploy_result == 'success' && inputs.e2e_test_result != 'skipped' && inputs.TEST_SUCCESS != 'true' shell: bash env: - DEPLOY_RESULT: ${{ inputs.deploy_result }} - E2E_TEST_RESULT: ${{ inputs.e2e_test_result }} - TEST_SUCCESS: ${{ inputs.TEST_SUCCESS }} - TEST_REPORT_URL: ${{ inputs.TEST_REPORT_URL }} - CONTAINER_WEB_APPURL: ${{ inputs.CONTAINER_WEB_APPURL }} - EXISTING_WEBAPP_URL: ${{ inputs.existing_webapp_url }} - RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }} - TEST_SUITE_NAME: ${{ steps.test_suite.outputs.TEST_SUITE_NAME }} - ACCELERATOR_NAME: ${{ env.accelerator_name }} GITHUB_REPOSITORY: ${{ github.repository }} GITHUB_RUN_ID: ${{ github.run_id }} + INPUT_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 }} + CLEANUP_STATUS: ${{ steps.cleanup.outputs.CLEANUP_STATUS }} + CONFIG_LABEL: ${{ steps.config.outputs.CONFIG_LABEL }} + RUN_E2E_TESTS: ${{ env.RUN_E2E_TESTS }} + TEST_SUITE_NAME: ${{ steps.test_suite.outputs.TEST_SUITE_NAME }} + INPUT_TEST_REPORT_URL: ${{ inputs.TEST_REPORT_URL }} run: | - RUN_URL="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" - WEBAPP_URL="${CONTAINER_WEB_APPURL:-${EXISTING_WEBAPP_URL}}" - RESOURCE_GROUP="${RESOURCE_GROUP_NAME}" + RUN_URL="https://github.com/${{ env.GITHUB_REPOSITORY }}/actions/runs/${{ env.GITHUB_RUN_ID }}" + TEST_REPORT_URL="$INPUT_TEST_REPORT_URL" + WEBAPP_URL="${INPUT_WEB_APPURL:-$INPUT_EXISTING_WEBAPP_URL}" + RESOURCE_GROUP="$INPUT_RESOURCE_GROUP_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" + "body": "

Dear Team,

We would like to inform you that ${ACCELERATOR_NAME} test automation has failed.

Status Summary:
StageStatus
Deployment✅ SUCCESS
E2E Tests❌ FAILED
Cleanup${CLEANUP_STATUS}

Deployment Details:
• Resource Group: ${RESOURCE_GROUP}
• Web App URL: ${WEBAPP_URL}
• Test Suite: ${TEST_SUITE_NAME}
• Test Report: View Report

Configuration: ${CONFIG_LABEL}

Run URL: ${RUN_URL}

Please investigate the matter at your earliest convenience.

Best regards,
Your Automation Team

", + "subject": "[CI/CD-Automation] [${ACCELERATOR_NAME}] E2E Test-Failed" } EOF ) @@ -385,57 +255,58 @@ jobs: if: inputs.deploy_result == 'skipped' && inputs.existing_webapp_url != '' && inputs.e2e_test_result == 'success' && (inputs.TEST_SUCCESS == 'true' || inputs.TEST_SUCCESS == '') shell: bash env: - DEPLOY_RESULT: ${{ inputs.deploy_result }} - EXISTING_WEBAPP_URL: ${{ inputs.existing_webapp_url }} - E2E_TEST_RESULT: ${{ inputs.e2e_test_result }} - TEST_SUCCESS: ${{ inputs.TEST_SUCCESS }} - TEST_REPORT_URL: ${{ inputs.TEST_REPORT_URL }} - TEST_SUITE_NAME: ${{ steps.test_suite.outputs.TEST_SUITE_NAME }} - ACCELERATOR_NAME: ${{ env.accelerator_name }} 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 }} + CLEANUP_STATUS: ${{ steps.cleanup.outputs.CLEANUP_STATUS }} + RUN_E2E_TESTS: ${{ env.RUN_E2E_TESTS }} + TEST_SUITE_NAME: ${{ steps.test_suite.outputs.TEST_SUITE_NAME }} run: | - RUN_URL="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" - EXISTING_URL="${EXISTING_WEBAPP_URL}" + 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" EMAIL_BODY=$(cat <Dear Team,

The ${ACCELERATOR_NAME} pipeline executed against the Specified WebApp 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" + "body": "

Dear Team,

The ${ACCELERATOR_NAME} pipeline executed against the specified Target URL and test automation has completed successfully.

Status Summary:
StageStatus
Deployment⏭️ SKIPPED (Tests executed on Pre-deployed RG)
E2E Tests✅ SUCCESS
Cleanup${CLEANUP_STATUS}

Test Results:
• Test Suite: ${TEST_SUITE_NAME}
${TEST_REPORT_URL:+• Test Report: View Report}
• Target URL: ${EXISTING_URL}

Run URL: ${RUN_URL}

Best regards,
Your Automation Team

", + "subject": "[CI/CD-Automation] [${ACCELERATOR_NAME}] Success" } EOF ) curl -X POST "${LOGICAPP_URL}" \ -H "Content-Type: application/json" \ - -d "$EMAIL_BODY" || echo "Failed to send success notification" + -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: - DEPLOY_RESULT: ${{ inputs.deploy_result }} - EXISTING_WEBAPP_URL: ${{ inputs.existing_webapp_url }} - E2E_TEST_RESULT: ${{ inputs.e2e_test_result }} - TEST_REPORT_URL: ${{ inputs.TEST_REPORT_URL }} - TEST_SUITE_NAME: ${{ steps.test_suite.outputs.TEST_SUITE_NAME }} - ACCELERATOR_NAME: ${{ env.accelerator_name }} 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 }} + CLEANUP_STATUS: ${{ steps.cleanup.outputs.CLEANUP_STATUS }} + RUN_E2E_TESTS: ${{ env.RUN_E2E_TESTS }} + TEST_SUITE_NAME: ${{ steps.test_suite.outputs.TEST_SUITE_NAME }} run: | - RUN_URL="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" - EXISTING_URL="${EXISTING_WEBAPP_URL}" + 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" EMAIL_BODY=$(cat <Dear Team,

The ${ACCELERATOR_NAME} pipeline executed against the Specified WebApp 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" + "body": "

Dear Team,

The ${ACCELERATOR_NAME} pipeline executed against the specified Target URL and test automation has failed.

Status Summary:
StageStatus
Deployment⏭️ SKIPPED (Tests executed on Pre-deployed RG)
E2E Tests❌ FAILED
Cleanup${CLEANUP_STATUS}

Failure Details:
• Target URL: ${EXISTING_URL}
${TEST_REPORT_URL:+• Test Report: View Report}
• Test Suite: ${TEST_SUITE_NAME}

Run URL: ${RUN_URL}

Best regards,
Your Automation Team

", + "subject": "[CI/CD-Automation] [${ACCELERATOR_NAME}] E2E Test-Failed" } EOF ) curl -X POST "${LOGICAPP_URL}" \ -H "Content-Type: application/json" \ - -d "$EMAIL_BODY" || echo "Failed to send test failure notification" + -d "$EMAIL_BODY" || echo "Failed to send existing URL test failure notification" From 88f78574241274486ca6177bb5f60a001e4ed67f Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Tue, 24 Mar 2026 10:49:04 +0530 Subject: [PATCH 2/5] fix: Update email subjects to include status icons --- .github/workflows/job-send-notification.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/job-send-notification.yml b/.github/workflows/job-send-notification.yml index 87154f99..63f414ce 100644 --- a/.github/workflows/job-send-notification.yml +++ b/.github/workflows/job-send-notification.yml @@ -137,7 +137,7 @@ jobs: EMAIL_BODY=$(cat <Dear Team,

We would like to inform you that the ${ACCELERATOR_NAME} deployment has failed due to insufficient quota.

Status Summary:
StageStatus
Deployment❌ FAILED (Insufficient Quota)
E2E Tests⏭️ SKIPPED
Cleanup${CLEANUP_STATUS}

Configuration: ${CONFIG_LABEL}

Run URL: ${RUN_URL}

Please resolve the quota issue and retry the deployment.

Best regards,
Your Automation Team

", - "subject": "[CI/CD-Automation] [${ACCELERATOR_NAME}] Insufficient Quota" + "subject": "❌[CI/CD-Automation] [${ACCELERATOR_NAME}] Insufficient Quota" } EOF ) @@ -162,7 +162,7 @@ jobs: EMAIL_BODY=$(cat <Dear Team,

We would like to inform you that the ${ACCELERATOR_NAME} deployment has failed.

Status Summary:
StageStatus
Deployment❌ FAILED (Deployment Issue)
E2E Tests⏭️ SKIPPED
Cleanup${CLEANUP_STATUS}

Deployment Details:
• Resource Group: ${RESOURCE_GROUP}

Configuration: ${CONFIG_LABEL}

Run URL: ${RUN_URL}

Please investigate the deployment failure at your earliest convenience.

Best regards,
Your Automation Team

", - "subject": "[CI/CD-Automation] [${ACCELERATOR_NAME}] Deployment-Failed" + "subject": "❌[CI/CD-Automation] [${ACCELERATOR_NAME}] Deployment-Failed" } EOF ) @@ -199,7 +199,7 @@ jobs: EMAIL_BODY=$(cat <Dear Team,

We would like to inform you that the ${ACCELERATOR_NAME} deployment has completed successfully.

Status Summary:
StageStatus
Deployment✅ SUCCESS
E2E Tests⏭️ SKIPPED
Cleanup${CLEANUP_STATUS}

Deployment Details:
• Resource Group: ${RESOURCE_GROUP}
• Web App URL: ${WEBAPP_URL}

Configuration: ${CONFIG_LABEL}

Run URL: ${RUN_URL}

Best regards,
Your Automation Team

", - "subject": "[CI/CD-Automation] [${ACCELERATOR_NAME}] Success" + "subject": "✅[CI/CD-Automation] [${ACCELERATOR_NAME}] Success" } EOF ) @@ -207,7 +207,7 @@ jobs: EMAIL_BODY=$(cat <Dear Team,

We would like to inform you that the ${ACCELERATOR_NAME} deployment and test automation has completed successfully.

Status Summary:
StageStatus
Deployment✅ SUCCESS
E2E Tests✅ SUCCESS
Cleanup${CLEANUP_STATUS}

Deployment Details:
• Resource Group: ${RESOURCE_GROUP}
• Web App URL: ${WEBAPP_URL}
• Test Suite: ${TEST_SUITE_NAME}
• Test Report: View Report

Configuration: ${CONFIG_LABEL}

Run URL: ${RUN_URL}

Best regards,
Your Automation Team

", - "subject": "[CI/CD-Automation] [${ACCELERATOR_NAME}] Success" + "subject": "✅[CI/CD-Automation] [${ACCELERATOR_NAME}] Success" } EOF ) @@ -242,7 +242,7 @@ jobs: EMAIL_BODY=$(cat <Dear Team,

We would like to inform you that ${ACCELERATOR_NAME} test automation has failed.

Status Summary:
StageStatus
Deployment✅ SUCCESS
E2E Tests❌ FAILED
Cleanup${CLEANUP_STATUS}

Deployment Details:
• Resource Group: ${RESOURCE_GROUP}
• Web App URL: ${WEBAPP_URL}
• Test Suite: ${TEST_SUITE_NAME}
• Test Report: View Report

Configuration: ${CONFIG_LABEL}

Run URL: ${RUN_URL}

Please investigate the matter at your earliest convenience.

Best regards,
Your Automation Team

", - "subject": "[CI/CD-Automation] [${ACCELERATOR_NAME}] E2E Test-Failed" + "subject": "❌[CI/CD-Automation] [${ACCELERATOR_NAME}] E2E Test-Failed" } EOF ) @@ -272,7 +272,7 @@ jobs: EMAIL_BODY=$(cat <Dear Team,

The ${ACCELERATOR_NAME} pipeline executed against the specified Target URL and test automation has completed successfully.

Status Summary:
StageStatus
Deployment⏭️ SKIPPED (Tests executed on Pre-deployed RG)
E2E Tests✅ SUCCESS
Cleanup${CLEANUP_STATUS}

Test Results:
• Test Suite: ${TEST_SUITE_NAME}
${TEST_REPORT_URL:+• Test Report: View Report}
• Target URL: ${EXISTING_URL}

Run URL: ${RUN_URL}

Best regards,
Your Automation Team

", - "subject": "[CI/CD-Automation] [${ACCELERATOR_NAME}] Success" + "subject": "✅[CI/CD-Automation] [${ACCELERATOR_NAME}] Success" } EOF ) @@ -302,7 +302,7 @@ jobs: EMAIL_BODY=$(cat <Dear Team,

The ${ACCELERATOR_NAME} pipeline executed against the specified Target URL and test automation has failed.

Status Summary:
StageStatus
Deployment⏭️ SKIPPED (Tests executed on Pre-deployed RG)
E2E Tests❌ FAILED
Cleanup${CLEANUP_STATUS}

Failure Details:
• Target URL: ${EXISTING_URL}
${TEST_REPORT_URL:+• Test Report: View Report}
• Test Suite: ${TEST_SUITE_NAME}

Run URL: ${RUN_URL}

Best regards,
Your Automation Team

", - "subject": "[CI/CD-Automation] [${ACCELERATOR_NAME}] E2E Test-Failed" + "subject": "❌[CI/CD-Automation] [${ACCELERATOR_NAME}] E2E Test-Failed" } EOF ) From ffe43cae788fbab2f133f83950e6cb8d6b3ca67a Mon Sep 17 00:00:00 2001 From: Harsh-Microsoft Date: Tue, 24 Mar 2026 18:29:01 +0530 Subject: [PATCH 3/5] fix: Update dependencies in Bicep and JSON templates --- infra/main.bicep | 7 +++++++ infra/main.json | 15 +++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 38671957..229c0cef 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -1472,6 +1472,10 @@ module avmContainerApp_update 'br/public:avm/res/app/container-app:0.19.0' = { : [] } } + dependsOn: [ + cognitiveServicePrivateEndpoint + contentUnderstandingPrivateEndpoint + ] } module avmContainerApp_API_update 'br/public:avm/res/app/container-app:0.19.0' = { @@ -1595,6 +1599,9 @@ module avmContainerApp_API_update 'br/public:avm/res/app/container-app:0.19.0' = ] } } + dependsOn: [ + cognitiveServicePrivateEndpoint + ] } // ============ // diff --git a/infra/main.json b/infra/main.json index 2b87e970..f4049f54 100644 --- a/infra/main.json +++ b/infra/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.41.2.15936", - "templateHash": "13407662447721904016" + "templateHash": "12196124000444437279" }, "name": "Content Processing Solution Accelerator", "description": "Bicep template to deploy the Content Processing Solution Accelerator with AVM compliance." @@ -41701,9 +41701,9 @@ }, "dependsOn": [ "avmAiServices", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').contentUnderstanding)]", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').contentUnderstanding)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]", "virtualNetwork" ] @@ -45072,8 +45072,8 @@ }, "dependsOn": [ "avmAiServices_cu", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').contentUnderstanding)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]", "virtualNetwork" ] }, @@ -63527,7 +63527,9 @@ "dependsOn": [ "avmAppConfig", "avmContainerAppEnv", - "avmContainerRegistryReader" + "avmContainerRegistryReader", + "cognitiveServicePrivateEndpoint", + "contentUnderstandingPrivateEndpoint" ] }, "avmContainerApp_API_update": { @@ -65201,7 +65203,8 @@ "dependsOn": [ "avmAppConfig", "avmContainerAppEnv", - "avmContainerRegistryReader" + "avmContainerRegistryReader", + "cognitiveServicePrivateEndpoint" ] } }, From 297a4cf1ad2511a4670524368367189c1505c226 Mon Sep 17 00:00:00 2001 From: Prajwal D C Date: Wed, 25 Mar 2026 00:38:22 +0530 Subject: [PATCH 4/5] fix: Content understanding networking issue --- infra/main.bicep | 6 +- infra/main.json | 289 ++++++++++++++++++----------------------------- 2 files changed, 112 insertions(+), 183 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 229c0cef..6c98afad 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -814,7 +814,7 @@ module cognitiveServicePrivateEndpoint 'br/public:avm/res/network/private-endpoi } } -module avmAiServices_cu 'br/public:avm/res/cognitive-services/account:0.13.2' = { +module avmAiServices_cu 'br/public:avm/res/cognitive-services/account:0.14.1' = { name: take('avm.res.cognitive-services.account.content-understanding.${solutionSuffix}', 64) params: { @@ -873,6 +873,10 @@ module contentUnderstandingPrivateEndpoint 'br/public:avm/res/network/private-en name: 'aicu-dns-zone-cognitiveservices' privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.cognitiveServices]!.outputs.resourceId } + { + name: 'ai-services-dns-zone-aiservices' + privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.aiServices]!.outputs.resourceId + } { name: 'aicu-dns-zone-contentunderstanding' privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.contentUnderstanding]!.outputs.resourceId diff --git a/infra/main.json b/infra/main.json index f4049f54..b4735de6 100644 --- a/infra/main.json +++ b/infra/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.41.2.15936", - "templateHash": "12196124000444437279" + "templateHash": "892970507353265658" }, "name": "Content Processing Solution Accelerator", "description": "Bicep template to deploy the Content Processing Solution Accelerator with AVM compliance." @@ -41777,8 +41777,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.37.4.10188", - "templateHash": "9381727816193702843" + "version": "0.39.26.7824", + "templateHash": "6544538318162038728" }, "name": "Cognitive Services", "description": "This module deploys a Cognitive Service." @@ -43123,14 +43123,15 @@ "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" - } + }, + "isHSMManagedCMK": "[equals(tryGet(split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), ''), '/'), 7), 'managedHSMs')]" }, "resources": { "cMKKeyVault::cMKKey": { - "condition": "[and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), not(empty(tryGet(parameters('customerManagedKey'), 'keyName')))))]", + "condition": "[and(and(not(empty(parameters('customerManagedKey'))), not(variables('isHSMManagedCMK'))), and(not(empty(parameters('customerManagedKey'))), not(variables('isHSMManagedCMK'))))]", "existing": true, "type": "Microsoft.KeyVault/vaults/keys", - "apiVersion": "2024-11-01", + "apiVersion": "2025-05-01", "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[2]]", "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[4]]", "name": "[format('{0}/{1}', last(split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')), tryGet(parameters('customerManagedKey'), 'keyName'))]" @@ -43139,7 +43140,7 @@ "condition": "[parameters('enableTelemetry')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2024-03-01", - "name": "[format('46d3xbcp.res.cognitiveservices-account.{0}.{1}', replace('0.13.2', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "name": "[format('46d3xbcp.res.cognitiveservices-account.{0}.{1}', replace('0.14.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", "properties": { "mode": "Incremental", "template": { @@ -43156,10 +43157,10 @@ } }, "cMKKeyVault": { - "condition": "[not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId')))]", + "condition": "[and(not(empty(parameters('customerManagedKey'))), not(variables('isHSMManagedCMK')))]", "existing": true, "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2024-11-01", + "apiVersion": "2025-05-01", "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[2]]", "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[4]]", "name": "[last(split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/'))]" @@ -43193,7 +43194,7 @@ "allowedFqdnList": "[parameters('allowedFqdnList')]", "apiProperties": "[parameters('apiProperties')]", "disableLocalAuth": "[parameters('disableLocalAuth')]", - "encryption": "[if(not(empty(parameters('customerManagedKey'))), createObject('keySource', 'Microsoft.KeyVault', 'keyVaultProperties', createObject('identityClientId', if(not(empty(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), ''))), reference('cMKUserAssignedIdentity').clientId, null()), 'keyVaultUri', reference('cMKKeyVault').vaultUri, 'keyName', parameters('customerManagedKey').keyName, 'keyVersion', if(not(empty(coalesce(tryGet(parameters('customerManagedKey'), 'keyVersion'), ''))), tryGet(parameters('customerManagedKey'), 'keyVersion'), last(split(reference('cMKKeyVault::cMKKey').keyUriWithVersion, '/'))))), null())]", + "encryption": "[if(not(empty(parameters('customerManagedKey'))), createObject('keySource', 'Microsoft.KeyVault', 'keyVaultProperties', createObject('identityClientId', if(not(empty(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), ''))), reference('cMKUserAssignedIdentity').clientId, null()), 'keyVaultUri', if(not(variables('isHSMManagedCMK')), reference('cMKKeyVault').vaultUri, format('https://{0}.managedhsm.azure.net/', last(split(parameters('customerManagedKey').keyVaultResourceId, '/')))), 'keyName', parameters('customerManagedKey').keyName, 'keyVersion', if(not(empty(tryGet(parameters('customerManagedKey'), 'keyVersion'))), parameters('customerManagedKey').keyVersion, if(not(variables('isHSMManagedCMK')), last(split(reference('cMKKeyVault::cMKKey').keyUriWithVersion, '/')), fail('Managed HSM CMK encryption requires specifying the ''keyVersion''.'))))), null())]", "migrationToken": "[parameters('migrationToken')]", "restore": "[parameters('restore')]", "restrictOutboundNetworkAccess": "[parameters('restrictOutboundNetworkAccess')]", @@ -43322,7 +43323,7 @@ "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", + "apiVersion": "2025-04-01", "name": "[format('{0}-cognitiveService-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]", "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]", @@ -43378,8 +43379,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.34.44.8038", - "templateHash": "12389807800450456797" + "version": "0.38.5.1644", + "templateHash": "16604612898799598358" }, "name": "Private Endpoints", "description": "This module deploys a Private Endpoint." @@ -43406,115 +43407,8 @@ } }, "metadata": { - "__bicep_export!": true - } - }, - "ipConfigurationType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the resource that is unique within a resource group." - } - }, - "properties": { - "type": "object", - "properties": { - "groupId": { - "type": "string", - "metadata": { - "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string." - } - }, - "memberName": { - "type": "string", - "metadata": { - "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string." - } - }, - "privateIPAddress": { - "type": "string", - "metadata": { - "description": "Required. A private IP address obtained from the private endpoint's subnet." - } - } - }, - "metadata": { - "description": "Required. Properties of private endpoint IP configurations." - } - } - }, - "metadata": { - "__bicep_export!": true - } - }, - "privateLinkServiceConnectionType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the private link service connection." - } - }, - "properties": { - "type": "object", - "properties": { - "groupIds": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string array `[]`." - } - }, - "privateLinkServiceId": { - "type": "string", - "metadata": { - "description": "Required. The resource id of private link service." - } - }, - "requestMessage": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars." - } - } - }, - "metadata": { - "description": "Required. Properties of private link service connection." - } - } - }, - "metadata": { - "__bicep_export!": true - } - }, - "customDnsConfigType": { - "type": "object", - "properties": { - "fqdn": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. FQDN that resolves to private endpoint IP address." - } - }, - "ipAddresses": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "Required. A list of private IP addresses of the private endpoint." - } - } - }, - "metadata": { - "__bicep_export!": true + "__bicep_export!": true, + "description": "The type of a private dns zone group." } }, "lockType": { @@ -43538,12 +43432,19 @@ "metadata": { "description": "Optional. Specify the type of lock." } + }, + "notes": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the notes of the lock." + } } }, "metadata": { "description": "An AVM-aligned type for a lock.", "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" } } }, @@ -43565,6 +43466,7 @@ } }, "metadata": { + "description": "The type of a private DNS zone group configuration.", "__bicep_imported_from!": { "sourceTemplate": "private-dns-zone-group/main.bicep" } @@ -43641,7 +43543,7 @@ "metadata": { "description": "An AVM-aligned type for a role assignment.", "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" } } } @@ -43678,13 +43580,13 @@ }, "ipConfigurations": { "type": "array", - "items": { - "$ref": "#/definitions/ipConfigurationType" - }, - "nullable": true, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/ipConfigurations" + }, "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." - } + }, + "nullable": true }, "privateDnsZoneGroup": { "$ref": "#/definitions/privateDnsZoneGroupType", @@ -43719,40 +43621,43 @@ }, "tags": { "type": "object", - "nullable": true, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/tags" + }, "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." - } + }, + "nullable": true }, "customDnsConfigs": { "type": "array", - "items": { - "$ref": "#/definitions/customDnsConfigType" - }, - "nullable": true, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs" + }, "description": "Optional. Custom DNS configurations." - } + }, + "nullable": true }, "manualPrivateLinkServiceConnections": { "type": "array", - "items": { - "$ref": "#/definitions/privateLinkServiceConnectionType" - }, - "nullable": true, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/manualPrivateLinkServiceConnections" + }, "description": "Conditional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource. Required if `privateLinkServiceConnections` is empty." - } + }, + "nullable": true }, "privateLinkServiceConnections": { "type": "array", - "items": { - "$ref": "#/definitions/privateLinkServiceConnectionType" - }, - "nullable": true, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/privateLinkServiceConnections" + }, "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty." - } + }, + "nullable": true }, "enableTelemetry": { "type": "bool", @@ -43787,8 +43692,8 @@ "avmTelemetry": { "condition": "[parameters('enableTelemetry')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2024-03-01", - "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.11.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "apiVersion": "2025-04-01", + "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.11.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", "properties": { "mode": "Incremental", "template": { @@ -43806,7 +43711,7 @@ }, "privateEndpoint": { "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2024-05-01", + "apiVersion": "2024-10-01", "name": "[parameters('name')]", "location": "[parameters('location')]", "tags": "[parameters('tags')]", @@ -43838,7 +43743,7 @@ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", "properties": { "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", - "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]" }, "dependsOn": [ "privateEndpoint" @@ -43869,7 +43774,7 @@ "privateEndpoint_privateDnsZoneGroup": { "condition": "[not(empty(parameters('privateDnsZoneGroup')))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", + "apiVersion": "2025-04-01", "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]", "properties": { "expressionEvaluationOptions": { @@ -43894,8 +43799,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.34.44.8038", - "templateHash": "13997305779829540948" + "version": "0.38.5.1644", + "templateHash": "24141742673128945" }, "name": "Private Endpoint Private DNS Zone Groups", "description": "This module deploys a Private Endpoint Private DNS Zone Group." @@ -43919,7 +43824,8 @@ } }, "metadata": { - "__bicep_export!": true + "__bicep_export!": true, + "description": "The type of a private DNS zone group configuration." } } }, @@ -43949,33 +43855,30 @@ } } }, - "variables": { - "copy": [ - { - "name": "privateDnsZoneConfigsVar", - "count": "[length(parameters('privateDnsZoneConfigs'))]", - "input": { - "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId, '/')))]", - "properties": { - "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId]" - } - } - } - ] - }, "resources": { "privateEndpoint": { "existing": true, "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2024-05-01", + "apiVersion": "2024-10-01", "name": "[parameters('privateEndpointName')]" }, "privateDnsZoneGroup": { "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2024-05-01", + "apiVersion": "2024-10-01", "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", "properties": { - "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigsVar')]" + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDnsZoneConfigs'))]", + "input": { + "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')].privateDnsZoneResourceId, '/')))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')].privateDnsZoneResourceId]" + } + } + } + ] } } }, @@ -44036,14 +43939,15 @@ "metadata": { "description": "The location the resource was deployed into." }, - "value": "[reference('privateEndpoint', '2024-05-01', 'full').location]" + "value": "[reference('privateEndpoint', '2024-10-01', 'full').location]" }, "customDnsConfigs": { "type": "array", - "items": { - "$ref": "#/definitions/customDnsConfigType" - }, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs", + "output": true + }, "description": "The custom DNS configurations of the private endpoint." }, "value": "[reference('privateEndpoint').customDnsConfigs]" @@ -44076,7 +43980,7 @@ "secretsExport": { "condition": "[not(equals(parameters('secretsExportConfiguration'), null()))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", + "apiVersion": "2025-04-01", "name": "[format('{0}-secrets-kv', uniqueString(deployment().name, parameters('location')))]", "subscriptionId": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[2]]", "resourceGroup": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[4]]", @@ -44100,8 +44004,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.37.4.10188", - "templateHash": "10828079590669389085" + "version": "0.39.26.7824", + "templateHash": "356315690886888607" } }, "definitions": { @@ -44130,7 +44034,7 @@ "metadata": { "description": "An AVM-aligned type for the output of the secret set via the secrets export feature.", "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" } } }, @@ -44153,7 +44057,7 @@ "metadata": { "description": "An AVM-aligned type for the secret to set via the secrets export feature.", "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" } } } @@ -44179,7 +44083,7 @@ "keyVault": { "existing": true, "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2024-11-01", + "apiVersion": "2025-05-01", "name": "[parameters('keyVaultName')]" }, "secrets": { @@ -44188,7 +44092,7 @@ "count": "[length(parameters('secretsToSet'))]" }, "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2024-11-01", + "apiVersion": "2025-05-01", "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretsToSet')[copyIndex()].name)]", "properties": { "value": "[parameters('secretsToSet')[copyIndex()].value]" @@ -44297,6 +44201,22 @@ "networkInterfaceResourceIds": "[reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs.networkInterfaceResourceIds.value]" } } + }, + "primaryKey": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "The primary access key." + }, + "value": "[if(not(parameters('disableLocalAuth')), listKeys('cognitiveService', '2025-06-01').key1, null())]" + }, + "secondaryKey": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "The secondary access key." + }, + "value": "[if(not(parameters('disableLocalAuth')), listKeys('cognitiveService', '2025-06-01').key2, null())]" } } } @@ -44349,6 +44269,10 @@ "name": "aicu-dns-zone-cognitiveservices", "privateDnsZoneResourceId": "[reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)).outputs.resourceId.value]" }, + { + "name": "ai-services-dns-zone-aiservices", + "privateDnsZoneResourceId": "[reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)).outputs.resourceId.value]" + }, { "name": "aicu-dns-zone-contentunderstanding", "privateDnsZoneResourceId": "[reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').contentUnderstanding)).outputs.resourceId.value]" @@ -45072,6 +44996,7 @@ }, "dependsOn": [ "avmAiServices_cu", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').contentUnderstanding)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]", "virtualNetwork" From f21877e414a4ec7855f239e4fcef88ad45dca12b Mon Sep 17 00:00:00 2001 From: Thanusree-Microsoft <168087422+Thanusree-Microsoft@users.noreply.github.com> Date: Thu, 26 Mar 2026 17:25:48 +0530 Subject: [PATCH 5/5] Update RBAC role name in Deployment Guide --- docs/DeploymentGuide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/DeploymentGuide.md b/docs/DeploymentGuide.md index a04f3a3c..1ee9a35e 100644 --- a/docs/DeploymentGuide.md +++ b/docs/DeploymentGuide.md @@ -16,7 +16,7 @@ Ensure you have access to an [Azure subscription](https://azure.microsoft.com/fr |------------------------------|-----------|-------------| | **Contributor** | Subscription or Resource Group | Create and manage Azure resources | | **User Access Administrator** | Subscription or Resource Group | Manage user access and role assignments | -| **Role Based Access Control** | Subscription/Resource Group level | Configure RBAC permissions | +| **Role Based Access Control Admin** | Subscription/Resource Group level | Configure RBAC permissions | | **Application Administrator** | Tenant | Create app registrations for authentication | **🔍 How to Check Your Permissions:**