diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 000000000..6aa4847d9
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,4 @@
+FROM mcr.microsoft.com/devcontainers/python:3.11-bullseye
+
+# Remove Yarn repository to avoid GPG key expiration issue
+RUN rm -f /etc/apt/sources.list.d/yarn.list
\ No newline at end of file
diff --git a/.github/workflows/deploy-linux.yml b/.github/workflows/deploy-linux.yml
index 3ddfc578e..c45aae3f5 100644
--- a/.github/workflows/deploy-linux.yml
+++ b/.github/workflows/deploy-linux.yml
@@ -93,21 +93,191 @@ on:
schedule:
- cron: '0 9,21 * * *' # Runs at 9:00 AM and 9:00 PM GMT
-
+permissions:
+ contents: read
+ actions: read
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-]+/[Rr]esource[Gg]roups/[^/]+/providers/[Mm]icrosoft\.[Oo]perational[Ii]nsights/[Ww]orkspaces/[^/]+$ ]]; 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-]+/[Rr]esource[Gg]roups/[^/]+/providers/([Mm]icrosoft\.[Mm]achine[Ll]earning[Ss]ervices/([Ww]orkspaces|[Pp]rojects)/[^/]+|[Mm]icrosoft\.[Cc]ognitive[Ss]ervices/[Aa]ccounts/[^/]+/[Pp]rojects/[^/]+)$ ]]; 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: ${{ github.event.inputs.azure_location || 'australiaeast' }}
- resource_group_name: ${{ github.event.inputs.resource_group_name || '' }}
- waf_enabled: ${{ github.event.inputs.waf_enabled == 'true' }}
- EXP: ${{ github.event.inputs.EXP == 'true' }}
- build_docker_image: ${{ github.event.inputs.build_docker_image == 'true' }}
- cleanup_resources: ${{ github.event.inputs.cleanup_resources == 'true' }}
- run_e2e_tests: ${{ github.event.inputs.run_e2e_tests || 'GoldenPath-Testing' }}
- AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ github.event.inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID || '' }}
- AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ github.event.inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID || '' }}
- existing_webapp_url: ${{ github.event.inputs.existing_webapp_url || '' }}
+ azure_location: ${{ needs.validate-inputs.outputs.azure_location || 'australiaeast' }}
+ 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 || 'GoldenPath-Testing' }}
+ 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
index 9e99cec18..31741f3b4 100644
--- a/.github/workflows/deploy-orchestrator.yml
+++ b/.github/workflows/deploy-orchestrator.yml
@@ -64,7 +64,10 @@ on:
env:
AZURE_DEV_COLLECT_TELEMETRY: ${{ vars.AZURE_DEV_COLLECT_TELEMETRY }}
-
+permissions:
+ contents: read
+ actions: read
+
jobs:
docker-build:
uses: ./.github/workflows/job-docker-build.yml
@@ -74,7 +77,7 @@ jobs:
secrets: inherit
deploy:
- if: always() && (inputs.trigger_type != 'workflow_dispatch' || inputs.existing_webapp_url == '' || inputs.existing_webapp_url == null)
+ 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:
@@ -94,7 +97,7 @@ jobs:
secrets: inherit
e2e-test:
- if: always() && ((needs.deploy.result == 'success' && needs.deploy.outputs.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))
+ if: "!cancelled() && ((needs.deploy.result == 'success' && needs.deploy.outputs.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:
@@ -103,7 +106,7 @@ jobs:
secrets: inherit
send-notification:
- if: always()
+ if: "!cancelled()"
needs: [docker-build, deploy, e2e-test]
uses: ./.github/workflows/job-send-notification.yml
with:
@@ -122,7 +125,7 @@ jobs:
secrets: inherit
cleanup-deployment:
- if: always() && needs.deploy.result == 'success' && needs.deploy.outputs.RESOURCE_GROUP_NAME != '' && inputs.existing_webapp_url == '' && (inputs.trigger_type != 'workflow_dispatch' || inputs.cleanup_resources)
+ 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:
diff --git a/.github/workflows/deploy-windows.yml b/.github/workflows/deploy-windows.yml
index 4cd93c4fd..9aec336a2 100644
--- a/.github/workflows/deploy-windows.yml
+++ b/.github/workflows/deploy-windows.yml
@@ -83,21 +83,192 @@ on:
# schedule:
# - cron: '0 9,21 * * *' # Runs at 9:00 AM and 9:00 PM GMT
-
+permissions:
+ contents: read
+ actions: read
+
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-]+/[Rr]esource[Gg]roups/[^/]+/providers/[Mm]icrosoft\.[Oo]perational[Ii]nsights/[Ww]orkspaces/[^/]+$ ]]; 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-]+/[Rr]esource[Gg]roups/[^/]+/providers/([Mm]icrosoft\.[Mm]achine[Ll]earning[Ss]ervices/([Ww]orkspaces|[Pp]rojects)/[^/]+|[Mm]icrosoft\.[Cc]ognitive[Ss]ervices/[Aa]ccounts/[^/]+/[Pp]rojects/[^/]+)$ ]]; 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: ${{ github.event.inputs.azure_location || 'australiaeast' }}
- resource_group_name: ${{ github.event.inputs.resource_group_name || '' }}
- waf_enabled: ${{ github.event.inputs.waf_enabled == 'true' }}
- EXP: ${{ github.event.inputs.EXP == 'true' }}
- build_docker_image: ${{ github.event.inputs.build_docker_image == 'true' }}
- cleanup_resources: ${{ github.event.inputs.cleanup_resources == 'true' }}
- run_e2e_tests: ${{ github.event.inputs.run_e2e_tests || 'GoldenPath-Testing' }}
- AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ github.event.inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID || '' }}
- AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ github.event.inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID || '' }}
- existing_webapp_url: ${{ github.event.inputs.existing_webapp_url || '' }}
+ azure_location: ${{ needs.validate-inputs.outputs.azure_location || 'australiaeast' }}
+ 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 || 'GoldenPath-Testing' }}
+ 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/job-cleanup-deployment.yml b/.github/workflows/job-cleanup-deployment.yml
index 6b920a4ed..0e8aef426 100644
--- a/.github/workflows/job-cleanup-deployment.yml
+++ b/.github/workflows/job-cleanup-deployment.yml
@@ -40,7 +40,10 @@ on:
description: 'Docker Image Tag'
required: true
type: string
-
+permissions:
+ contents: read
+ actions: read
+
jobs:
cleanup-deployment:
runs-on: ${{ inputs.runner_os }}
@@ -52,14 +55,6 @@ jobs:
ENV_NAME: ${{ inputs.ENV_NAME }}
IMAGE_TAG: ${{ inputs.IMAGE_TAG }}
steps:
- - name: Setup Azure CLI
- shell: bash
- run: |
- if [[ "${{ runner.os }}" == "Linux" ]]; then
- curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
- fi
- az --version
-
- name: Login to Azure
shell: bash
run: |
diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml
index 20a8591d4..37d1b82a2 100644
--- a/.github/workflows/job-deploy-linux.yml
+++ b/.github/workflows/job-deploy-linux.yml
@@ -38,7 +38,10 @@ on:
WEB_APPURL:
description: "Container Web App URL"
value: ${{ jobs.deploy-linux.outputs.WEB_APPURL }}
-
+permissions:
+ contents: read
+ actions: read
+
jobs:
deploy-linux:
runs-on: ubuntu-latest
@@ -47,30 +50,155 @@ jobs:
outputs:
WEB_APPURL: ${{ steps.get_output_linux.outputs.WEB_APPURL }}
steps:
+ - 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-]+/[Rr]esource[Gg]roups/[^/]+/providers/[Mm]icrosoft\.[Oo]perational[Ii]nsights/[Ww]orkspaces/[^/]+$ ]]; 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-]+/[Rr]esource[Gg]roups/[^/]+/providers/([Mm]icrosoft\.[Mm]achine[Ll]earning[Ss]ervices/([Ww]orkspaces|[Pp]rojects)/[^/]+|[Mm]icrosoft\.[Cc]ognitive[Ss]ervices/[Aa]ccounts/[^/]+/[Pp]rojects/[^/]+)$ ]]; 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: Checkout Code
uses: actions/checkout@v4
- name: Configure Parameters Based on WAF Setting
shell: bash
+ env:
+ WAF_ENABLED: ${{ inputs.WAF_ENABLED }}
run: |
- if [[ "${{ inputs.WAF_ENABLED }}" == "true" ]]; then
+ if [[ "$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: Setup Azure CLI
- shell: bash
- run: |
- curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
- - name: Setup Azure Developer CLI (Linux)
- if: runner.os == 'Linux'
- shell: bash
- run: |
- curl -fsSL https://aka.ms/install-azd.sh | sudo bash
- azd version
+ - name: Install azd
+ uses: Azure/setup-azd@v2
- name: Login to AZD
id: login-azure
@@ -83,45 +211,55 @@ jobs:
- name: Deploy using azd up and extract values (Linux)
id: get_output_linux
shell: bash
+ env:
+ ENV_NAME: ${{ inputs.ENV_NAME }}
+ AZURE_ENV_OPENAI_LOCATION: ${{ inputs.AZURE_ENV_OPENAI_LOCATION }}
+ AZURE_LOCATION: ${{ inputs.AZURE_LOCATION }}
+ RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ IMAGE_TAG: ${{ inputs.IMAGE_TAG }}
+ BUILD_DOCKER_IMAGE: ${{ inputs.BUILD_DOCKER_IMAGE }}
+ 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 ${{ inputs.ENV_NAME }} --no-prompt
- echo "Environment created: ${{ inputs.ENV_NAME }}"
+ azd env new "$ENV_NAME" --no-prompt
+ echo "Environment created: $ENV_NAME"
echo "Setting default subscription..."
- azd config set defaults.subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }}
+ 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_OPENAI_LOCATION="${{ inputs.AZURE_ENV_OPENAI_LOCATION }}"
- azd env set AZURE_LOCATION="${{ inputs.AZURE_LOCATION }}"
- azd env set AZURE_RESOURCE_GROUP="${{ inputs.RESOURCE_GROUP_NAME }}"
- azd env set AZURE_ENV_IMAGETAG="${{ inputs.IMAGE_TAG }}"
+ azd env set AZURE_ENV_OPENAI_LOCATION="$AZURE_ENV_OPENAI_LOCATION"
+ azd env set AZURE_LOCATION="$AZURE_LOCATION"
+ azd env set AZURE_RESOURCE_GROUP="$RESOURCE_GROUP_NAME"
+ azd env set AZURE_ENV_IMAGETAG="$IMAGE_TAG"
# Set ACR name only when building Docker image
- if [[ "${{ inputs.BUILD_DOCKER_IMAGE }}" == "true" ]]; then
+ if [[ "$BUILD_DOCKER_IMAGE" == "true" ]]; then
# Extract ACR name from login server and set as environment variable
- ACR_NAME=$(echo "${{ secrets.ACR_TEST_USERNAME }}")
+ ACR_NAME="${{ secrets.ACR_TEST_USERNAME }}"
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 [[ "${{ inputs.EXP }}" == "true" ]]; then
+ if [[ "$EXP" == "true" ]]; then
echo "✅ EXP ENABLED - Setting EXP parameters..."
# Set EXP variables dynamically
- if [[ -n "${{ github.event.inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}" ]]; then
- EXP_LOG_ANALYTICS_ID="${{ github.event.inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}"
+ 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="${{ secrets.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}"
fi
- if [[ -n "${{ github.event.inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" ]]; then
- EXP_AI_PROJECT_ID="${{ github.event.inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}"
+ 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="${{ secrets.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}"
fi
@@ -132,7 +270,7 @@ jobs:
azd env set AZURE_EXISTING_AI_PROJECT_RESOURCE_ID="$EXP_AI_PROJECT_ID"
else
echo "❌ EXP DISABLED - Skipping EXP parameters"
- if [[ -n "${{ github.event.inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}" ]] || [[ -n "${{ github.event.inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" ]]; then
+ if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]] || [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then
echo "⚠️ Warning: EXP parameter values provided but EXP is disabled. These values will be ignored."
fi
fi
@@ -151,26 +289,25 @@ jobs:
fi
# Extract values from azd output (adjust these based on actual output variable names)
- export AI_FOUNDRY_RESOURCE_ID=$(echo "$DEPLOY_OUTPUT" | jq -r '.AI_FOUNDRY_RESOURCE_ID // empty')
+ AI_FOUNDRY_RESOURCE_ID=$(echo "$DEPLOY_OUTPUT" | jq -r '.AI_FOUNDRY_RESOURCE_ID // empty')
echo "AI_FOUNDRY_RESOURCE_ID=$AI_FOUNDRY_RESOURCE_ID" >> $GITHUB_ENV
- export AI_SEARCH_SERVICE_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.AI_SEARCH_SERVICE_NAME // empty')
+ AI_SEARCH_SERVICE_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.AI_SEARCH_SERVICE_NAME // empty')
echo "AI_SEARCH_SERVICE_NAME=$AI_SEARCH_SERVICE_NAME" >> $GITHUB_ENV
- export AZURE_COSMOSDB_ACCOUNT=$(echo "$DEPLOY_OUTPUT" | jq -r '.AZURE_COSMOSDB_ACCOUNT // empty')
+ AZURE_COSMOSDB_ACCOUNT=$(echo "$DEPLOY_OUTPUT" | jq -r '.AZURE_COSMOSDB_ACCOUNT // empty')
echo "AZURE_COSMOSDB_ACCOUNT=$AZURE_COSMOSDB_ACCOUNT" >> $GITHUB_ENV
- export STORAGE_ACCOUNT_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.STORAGE_ACCOUNT_NAME // empty')
+ STORAGE_ACCOUNT_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.STORAGE_ACCOUNT_NAME // empty')
echo "STORAGE_ACCOUNT_NAME=$STORAGE_ACCOUNT_NAME" >> $GITHUB_ENV
- export STORAGE_CONTAINER_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.STORAGE_CONTAINER_NAME // empty')
+ STORAGE_CONTAINER_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.STORAGE_CONTAINER_NAME // empty')
echo "STORAGE_CONTAINER_NAME=$STORAGE_CONTAINER_NAME" >> $GITHUB_ENV
- export KEY_VAULT_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.KEY_VAULT_NAME // empty')
+ KEY_VAULT_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.KEY_VAULT_NAME // empty')
echo "KEY_VAULT_NAME=$KEY_VAULT_NAME" >> $GITHUB_ENV
- export RESOURCE_GROUP_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.RESOURCE_GROUP_NAME // .AZURE_RESOURCE_GROUP // empty')
- [[ -z "$RESOURCE_GROUP_NAME" ]] && export RESOURCE_GROUP_NAME="$RESOURCE_GROUP_NAME"
+ RESOURCE_GROUP_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.RESOURCE_GROUP_NAME // empty')
echo "RESOURCE_GROUP_NAME=$RESOURCE_GROUP_NAME" >> $GITHUB_ENV
WEB_APPURL=$(echo "$DEPLOY_OUTPUT" | jq -r '.WEB_APP_URL // .SERVICE_BACKEND_ENDPOINT_URL // empty')
@@ -181,9 +318,17 @@ jobs:
id: post_deploy
env:
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
+ AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
+ STORAGE_ACCOUNT_NAME: ${{ env.STORAGE_ACCOUNT_NAME }}
+ STORAGE_CONTAINER_NAME: ${{ env.STORAGE_CONTAINER_NAME }}
+ KEY_VAULT_NAME: ${{ env.KEY_VAULT_NAME }}
+ AZURE_COSMOSDB_ACCOUNT: ${{ env.AZURE_COSMOSDB_ACCOUNT }}
+ RESOURCE_GROUP_NAME: ${{ env.RESOURCE_GROUP_NAME }}
+ AI_SEARCH_SERVICE_NAME: ${{ env.AI_SEARCH_SERVICE_NAME }}
+ AI_FOUNDRY_RESOURCE_ID: ${{ env.AI_FOUNDRY_RESOURCE_ID }}
run: |
set -e
- az account set --subscription "${{ secrets.AZURE_SUBSCRIPTION_ID }}"
+ az account set --subscription "$AZURE_SUBSCRIPTION_ID"
echo "Running post-deployment script..."
@@ -194,26 +339,54 @@ jobs:
"$AZURE_COSMOSDB_ACCOUNT" \
"$RESOURCE_GROUP_NAME" \
"$AI_SEARCH_SERVICE_NAME" \
- "${{ secrets.AZURE_CLIENT_ID }}" \
+ "$AZURE_CLIENT_ID" \
"$AI_FOUNDRY_RESOURCE_ID"
- name: Generate Deploy Job Summary
if: always()
+ env:
+ RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ WAF_ENABLED: ${{ inputs.WAF_ENABLED }}
+ EXP: ${{ inputs.EXP }}
+ AZURE_LOCATION: ${{ inputs.AZURE_LOCATION }}
+ AZURE_ENV_OPENAI_LOCATION: ${{ inputs.AZURE_ENV_OPENAI_LOCATION }}
+ IMAGE_TAG: ${{ inputs.IMAGE_TAG }}
+ JOB_STATUS: ${{ job.status }}
+ WEB_APPURL: ${{ steps.get_output_linux.outputs.WEB_APPURL }}
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** | \`${{ inputs.RESOURCE_GROUP_NAME }}\` |" >> $GITHUB_STEP_SUMMARY
- echo "| **Configuration 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' }}\` |" >> $GITHUB_STEP_SUMMARY
- echo "| **Azure Region (Infrastructure)** | \`${{ inputs.AZURE_LOCATION }}\` |" >> $GITHUB_STEP_SUMMARY
- echo "| **Azure OpenAI Region** | \`${{ inputs.AZURE_ENV_OPENAI_LOCATION }}\` |" >> $GITHUB_STEP_SUMMARY
- echo "| **Docker Image Tag** | \`${{ inputs.IMAGE_TAG }}\` |" >> $GITHUB_STEP_SUMMARY
+
+ if [[ "$JOB_STATUS" == "success" ]]; then
+ echo "| **Job Status** | ✅ Success |" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "| **Job Status** | ❌ Failed |" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ echo "| **Resource Group** | \`$RESOURCE_GROUP_NAME\` |" >> $GITHUB_STEP_SUMMARY
+
+ # Determine configuration type
+ if [[ "$WAF_ENABLED" == "true" && "$EXP" == "true" ]]; then
+ CONFIG_TYPE="WAF + EXP"
+ elif [[ "$WAF_ENABLED" == "true" && "$EXP" != "true" ]]; then
+ CONFIG_TYPE="WAF + Non-EXP"
+ elif [[ "$WAF_ENABLED" != "true" && "$EXP" == "true" ]]; then
+ CONFIG_TYPE="Non-WAF + EXP"
+ else
+ CONFIG_TYPE="Non-WAF + Non-EXP"
+ fi
+ echo "| **Configuration Type** | \`$CONFIG_TYPE\` |" >> $GITHUB_STEP_SUMMARY
+
+ echo "| **Azure Region (Infrastructure)** | \`$AZURE_LOCATION\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Azure OpenAI Region** | \`$AZURE_ENV_OPENAI_LOCATION\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Docker Image Tag** | \`$IMAGE_TAG\` |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
- if [[ "${{ job.status }}" == "success" ]]; then
+
+ if [[ "$JOB_STATUS" == "success" ]]; then
echo "### ✅ Deployment Details" >> $GITHUB_STEP_SUMMARY
- echo "- **Web App URL**: [${{ steps.get_output_linux.outputs.WEB_APPURL }}](${{ steps.get_output_linux.outputs.WEB_APPURL }})" >> $GITHUB_STEP_SUMMARY
+ echo "- **Web App URL**: [$WEB_APPURL]($WEB_APPURL)" >> $GITHUB_STEP_SUMMARY
echo "- Successfully deployed to Azure with all resources configured" >> $GITHUB_STEP_SUMMARY
echo "- Post-deployment scripts executed successfully" >> $GITHUB_STEP_SUMMARY
else
diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml
index eb4cd0b69..e9dda12d4 100644
--- a/.github/workflows/job-deploy-windows.yml
+++ b/.github/workflows/job-deploy-windows.yml
@@ -38,7 +38,10 @@ on:
WEB_APPURL:
description: "Container Web App URL"
value: ${{ jobs.deploy-windows.outputs.WEB_APPURL }}
-
+permissions:
+ contents: read
+ actions: read
+
jobs:
deploy-windows:
runs-on: windows-latest
@@ -47,13 +50,148 @@ jobs:
outputs:
WEB_APPURL: ${{ steps.get_output_windows.outputs.WEB_APPURL }}
steps:
+ - 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-]+/[Rr]esource[Gg]roups/[^/]+/providers/[Mm]icrosoft\.[Oo]perational[Ii]nsights/[Ww]orkspaces/[^/]+$ ]]; 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-]+/[Rr]esource[Gg]roups/[^/]+/providers/([Mm]icrosoft\.[Mm]achine[Ll]earning[Ss]ervices/([Ww]orkspaces|[Pp]rojects)/[^/]+|[Mm]icrosoft\.[Cc]ognitive[Ss]ervices/[Aa]ccounts/[^/]+/[Pp]rojects/[^/]+)$ ]]; 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: Checkout Code
uses: actions/checkout@v4
- name: Configure Parameters Based on WAF Setting
shell: bash
+ env:
+ WAF_ENABLED: ${{ inputs.WAF_ENABLED }}
run: |
- if [[ "${{ inputs.WAF_ENABLED }}" == "true" ]]; then
+ if [[ "$WAF_ENABLED" == "true" ]]; then
cp infra/main.waf.parameters.json infra/main.parameters.json
echo "✅ Successfully copied WAF parameters to main parameters file"
else
@@ -75,25 +213,35 @@ jobs:
- name: Deploy using azd up and extract values (Windows)
id: get_output_windows
shell: pwsh
+ env:
+ ENV_NAME: ${{ inputs.ENV_NAME }}
+ AZURE_ENV_OPENAI_LOCATION: ${{ inputs.AZURE_ENV_OPENAI_LOCATION }}
+ AZURE_LOCATION: ${{ inputs.AZURE_LOCATION }}
+ RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ IMAGE_TAG: ${{ inputs.IMAGE_TAG }}
+ BUILD_DOCKER_IMAGE: ${{ inputs.BUILD_DOCKER_IMAGE }}
+ 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 "Creating environment..."
- azd env new ${{ inputs.ENV_NAME }} --no-prompt
- Write-Host "Environment created: ${{ inputs.ENV_NAME }}"
+ azd env new $env:ENV_NAME --no-prompt
+ Write-Host "Environment created: $env: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_OPENAI_LOCATION="${{ inputs.AZURE_ENV_OPENAI_LOCATION }}"
- azd env set AZURE_LOCATION="${{ inputs.AZURE_LOCATION }}"
- azd env set AZURE_RESOURCE_GROUP="${{ inputs.RESOURCE_GROUP_NAME }}"
- azd env set AZURE_ENV_IMAGETAG="${{ inputs.IMAGE_TAG }}"
+ azd env set AZURE_ENV_OPENAI_LOCATION="$env:AZURE_ENV_OPENAI_LOCATION"
+ azd env set AZURE_LOCATION="$env:AZURE_LOCATION"
+ azd env set AZURE_RESOURCE_GROUP="$env:RESOURCE_GROUP_NAME"
+ azd env set AZURE_ENV_IMAGETAG="$env:IMAGE_TAG"
# Set ACR name only when building Docker image
- if ("${{ inputs.BUILD_DOCKER_IMAGE }}" -eq "true") {
+ if ($env:BUILD_DOCKER_IMAGE -eq "true") {
$ACR_NAME = "${{ secrets.ACR_TEST_USERNAME }}"
azd env set AZURE_ENV_ACR_NAME="$ACR_NAME"
Write-Host "Set ACR name to: $ACR_NAME"
@@ -101,18 +249,18 @@ jobs:
Write-Host "Skipping ACR name configuration (using existing image)"
}
- if ("${{ inputs.EXP }}" -eq "true") {
+ if ($env:EXP -eq "true") {
Write-Host "✅ EXP ENABLED - Setting EXP parameters..."
# Set EXP variables dynamically
- if ("${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}" -ne "") {
- $EXP_LOG_ANALYTICS_ID = "${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}"
+ 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 = "${{ secrets.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}"
}
- if ("${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" -ne "") {
- $EXP_AI_PROJECT_ID = "${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_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 = "${{ secrets.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}"
}
@@ -190,21 +338,46 @@ jobs:
- name: Generate Deploy Job Summary
if: always()
shell: bash
+ env:
+ RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
+ WAF_ENABLED: ${{ inputs.WAF_ENABLED }}
+ EXP: ${{ inputs.EXP }}
+ AZURE_LOCATION: ${{ inputs.AZURE_LOCATION }}
+ AZURE_ENV_OPENAI_LOCATION: ${{ inputs.AZURE_ENV_OPENAI_LOCATION }}
+ IMAGE_TAG: ${{ inputs.IMAGE_TAG }}
+ JOB_STATUS: ${{ job.status }}
+ WEB_APPURL: ${{ steps.get_output_windows.outputs.WEB_APPURL }}
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
- echo "| **Resource Group** | \`${{ inputs.RESOURCE_GROUP_NAME }}\` |" >> $GITHUB_STEP_SUMMARY
- echo "| **Configuration 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' }}\` |" >> $GITHUB_STEP_SUMMARY
- echo "| **Azure Region (Infrastructure)** | \`${{ inputs.AZURE_LOCATION }}\` |" >> $GITHUB_STEP_SUMMARY
- echo "| **Azure OpenAI Region** | \`${{ inputs.AZURE_ENV_OPENAI_LOCATION }}\` |" >> $GITHUB_STEP_SUMMARY
- echo "| **Docker Image Tag** | \`${{ inputs.IMAGE_TAG }}\` |" >> $GITHUB_STEP_SUMMARY
+ if [[ "$JOB_STATUS" == "success" ]]; then
+ echo "| **Job Status** | ✅ Success |" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "| **Job Status** | ❌ Failed |" >> $GITHUB_STEP_SUMMARY
+ fi
+ echo "| **Resource Group** | \`$RESOURCE_GROUP_NAME\` |" >> $GITHUB_STEP_SUMMARY
+
+ # Determine configuration type
+ if [[ "$WAF_ENABLED" == "true" && "$EXP" == "true" ]]; then
+ CONFIG_TYPE="WAF + EXP"
+ elif [[ "$WAF_ENABLED" == "true" && "$EXP" != "true" ]]; then
+ CONFIG_TYPE="WAF + Non-EXP"
+ elif [[ "$WAF_ENABLED" != "true" && "$EXP" == "true" ]]; then
+ CONFIG_TYPE="Non-WAF + EXP"
+ else
+ CONFIG_TYPE="Non-WAF + Non-EXP"
+ fi
+ echo "| **Configuration Type** | \`$CONFIG_TYPE\` |" >> $GITHUB_STEP_SUMMARY
+
+ echo "| **Azure Region (Infrastructure)** | \`$AZURE_LOCATION\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Azure OpenAI Region** | \`$AZURE_ENV_OPENAI_LOCATION\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| **Docker Image Tag** | \`$IMAGE_TAG\` |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
- if [[ "${{ job.status }}" == "success" ]]; then
+ if [[ "$JOB_STATUS" == "success" ]]; then
echo "### ✅ Deployment Details" >> $GITHUB_STEP_SUMMARY
- echo "- **Web App URL**: [${{ steps.get_output_windows.outputs.WEB_APPURL }}](${{ steps.get_output_windows.outputs.WEB_APPURL }})" >> $GITHUB_STEP_SUMMARY
+ echo "- **Web App URL**: [$WEB_APPURL]($WEB_APPURL)" >> $GITHUB_STEP_SUMMARY
echo "- Successfully deployed to Azure with all resources configured" >> $GITHUB_STEP_SUMMARY
echo "- Post-deployment scripts executed successfully" >> $GITHUB_STEP_SUMMARY
else
diff --git a/.github/workflows/job-deploy.yml b/.github/workflows/job-deploy.yml
index 218b9fc74..a54023768 100644
--- a/.github/workflows/job-deploy.yml
+++ b/.github/workflows/job-deploy.yml
@@ -98,7 +98,10 @@ env:
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 }}
-
+permissions:
+ contents: read
+ actions: read
+
jobs:
azure-setup:
name: Azure Setup
@@ -111,27 +114,208 @@ jobs:
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 }}
-
+ EXP_ENABLED: ${{ steps.configure_exp.outputs.EXP_ENABLED }}
+
steps:
+ - name: Validate Workflow Input Parameters
+ shell: bash
+ env:
+ INPUT_TRIGGER_TYPE: ${{ inputs.trigger_type }}
+ INPUT_RUNNER_OS: ${{ inputs.runner_os }}
+ INPUT_BUILD_DOCKER_IMAGE: ${{ inputs.build_docker_image }}
+ INPUT_AZURE_LOCATION: ${{ inputs.azure_location }}
+ INPUT_RESOURCE_GROUP_NAME: ${{ inputs.resource_group_name }}
+ INPUT_WAF_ENABLED: ${{ inputs.waf_enabled }}
+ INPUT_EXP: ${{ inputs.EXP }}
+ 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_EXISTING_WEBAPP_URL: ${{ inputs.existing_webapp_url }}
+ INPUT_DOCKER_IMAGE_TAG: ${{ inputs.docker_image_tag }}
+ 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 runner_os (required - must be specific values)
+ ALLOWED_RUNNER_OS=("ubuntu-latest" "windows-latest")
+ if [[ -z "$INPUT_RUNNER_OS" ]]; then
+ echo "❌ ERROR: runner_os is required but was not provided"
+ VALIDATION_FAILED=true
+ elif [[ ! " ${ALLOWED_RUNNER_OS[@]} " =~ " ${INPUT_RUNNER_OS} " ]]; then
+ echo "❌ ERROR: runner_os '$INPUT_RUNNER_OS' is invalid. Allowed values: ${ALLOWED_RUNNER_OS[*]}"
+ VALIDATION_FAILED=true
+ else
+ echo "✅ runner_os: '$INPUT_RUNNER_OS' is valid"
+ fi
+
+ # Validate build_docker_image (boolean)
+ 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 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-]+/[Rr]esource[Gg]roups/[^/]+/providers/[Mm]icrosoft\.[Oo]perational[Ii]nsights/[Ww]orkspaces/[^/]+$ ]]; 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-]+/[Rr]esource[Gg]roups/[^/]+/providers/([Mm]icrosoft\.[Mm]achine[Ll]earning[Ss]ervices/([Ww]orkspaces|[Pp]rojects)/[^/]+|[Mm]icrosoft\.[Cc]ognitive[Ss]ervices/[Aa]ccounts/[^/]+/[Pp]rojects/[^/]+)$ ]]; 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 existing_webapp_url (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
+ 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
+ id: configure_exp
shell: bash
+ env:
+ INPUT_EXP: ${{ inputs.EXP }}
+ INPUT_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}
+ INPUT_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}
run: |
echo "🔍 Validating EXP configuration..."
- if [[ "${{ inputs.EXP }}" != "true" ]]; then
- if [[ -n "${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}" ]] || [[ -n "${{ inputs.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 "${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}" ]] && echo " - Azure Log Analytics Workspace ID: '${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}'"
- [[ -n "${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" ]] && echo " - Azure AI Project Resource ID: '${{ inputs.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
+ EXP_ENABLED="false"
+
+ if [[ "$INPUT_EXP" == "true" ]]; then
+ EXP_ENABLED="true"
+ echo "✅ EXP explicitly enabled by user input"
+ elif [[ -n "$INPUT_LOG_ANALYTICS_WORKSPACE_ID" ]] || [[ -n "$INPUT_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_LOG_ANALYTICS_WORKSPACE_ID" ]] && echo " - Azure Log Analytics Workspace ID: '$INPUT_LOG_ANALYTICS_WORKSPACE_ID'"
+ [[ -n "$INPUT_AI_PROJECT_RESOURCE_ID" ]] && echo " - Azure AI Project Resource ID: '$INPUT_AI_PROJECT_RESOURCE_ID'"
+ echo ""
+ echo "✅ Automatically enabling EXP to use these values."
+ EXP_ENABLED="true"
fi
+ echo "EXP_ENABLED=$EXP_ENABLED" >> $GITHUB_ENV
+ echo "EXP_ENABLED=$EXP_ENABLED" >> $GITHUB_OUTPUT
+ echo "Final EXP status: $EXP_ENABLED"
+
+
- name: Checkout Code
uses: actions/checkout@v4
@@ -143,21 +327,22 @@ jobs:
- 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 }}
+ GPT_MIN_CAPACITY: ${{ env.GPT_MIN_CAPACITY }}
+ TEXT_EMBEDDING_MIN_CAPACITY: ${{ env.TEXT_EMBEDDING_MIN_CAPACITY }}
+ AZURE_REGIONS: ${{ vars.AZURE_REGIONS }}
run: |
- export AZURE_CLIENT_ID=${{ secrets.AZURE_CLIENT_ID }}
- export AZURE_TENANT_ID=${{ secrets.AZURE_TENANT_ID }}
- export AZURE_CLIENT_SECRET=${{ secrets.AZURE_CLIENT_SECRET }}
- export AZURE_SUBSCRIPTION_ID="${{ secrets.AZURE_SUBSCRIPTION_ID }}"
- export GPT_MIN_CAPACITY=${{ env.GPT_MIN_CAPACITY }}
- export TEXT_EMBEDDING_MIN_CAPACITY=${{ env.TEXT_EMBEDDING_MIN_CAPACITY }}
- export AZURE_REGIONS="${{ vars.AZURE_REGIONS }}"
-
chmod +x scripts/checkquota.sh
if ! scripts/checkquota.sh; then
+ # If quota check fails due to insufficient quota, set the flag
if grep -q "No region with sufficient quota found" scripts/checkquota.sh; then
echo "QUOTA_FAILED=true" >> $GITHUB_ENV
fi
- exit 1
+ exit 1 # Fail the pipeline if any other failure occurs
fi
- name: Set Quota Failure Output
@@ -176,13 +361,15 @@ jobs:
- name: Set Deployment Region
id: set_region
shell: bash
+ env:
+ 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 [[ "${{ inputs.trigger_type }}" == "workflow_dispatch" && -n "${{ inputs.azure_location }}" ]]; then
- USER_SELECTED_LOCATION="${{ inputs.azure_location }}"
+ if [[ "${{ inputs.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
@@ -195,14 +382,16 @@ jobs:
- 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 "${{ inputs.resource_group_name }}" ]]; then
- echo "Using provided Resource Group name: ${{ inputs.resource_group_name }}"
- echo "RESOURCE_GROUP_NAME=${{ inputs.resource_group_name }}" >> $GITHUB_ENV
+ 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="docgenv2" # Account name as specified
+ ACCL_NAME="docgen" # 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
@@ -244,10 +433,12 @@ jobs:
- name: Determine Docker Image Tag
id: determine_image_tag
+ env:
+ INPUT_DOCKER_IMAGE_TAG: ${{ inputs.docker_image_tag }}
run: |
if [[ "${{ env.BUILD_DOCKER_IMAGE }}" == "true" ]]; then
- if [[ -n "${{ inputs.docker_image_tag }}" ]]; then
- IMAGE_TAG="${{ inputs.docker_image_tag }}"
+ 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"
@@ -293,26 +484,27 @@ jobs:
- name: Display Workflow Configuration to GitHub Summary
shell: bash
+ env:
+ 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 "| **Trigger Type** | \`${{ github.event_name }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| **Branch** | \`${{ env.BRANCH_NAME }}\` |" >> $GITHUB_STEP_SUMMARY
- echo "| **Runner OS** | \`${{ inputs.runner_os }}\` |" >> $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 "| **EXP Enabled** | ${{ steps.configure_exp.outputs.EXP_ENABLED == '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 [[ "${{ inputs.trigger_type }}" == "workflow_dispatch" && -n "${{ inputs.azure_location }}" ]]; then
- echo "| **Azure Location** | \`${{ inputs.azure_location }}\` (User Selected) |" >> $GITHUB_STEP_SUMMARY
+ if [[ "${{ inputs.trigger_type }}" == "workflow_dispatch" && -n "$INPUT_AZURE_LOCATION" ]]; then
+ echo "| **Azure Location** | \`$INPUT_AZURE_LOCATION\` (User Selected) |" >> $GITHUB_STEP_SUMMARY
fi
- if [[ -n "${{ inputs.resource_group_name }}" ]]; then
- echo "| **Resource Group** | \`${{ inputs.resource_group_name }}\` (Pre-specified) |" >> $GITHUB_STEP_SUMMARY
+ 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
@@ -328,7 +520,7 @@ jobs:
deploy-linux:
name: Deploy on Linux
needs: azure-setup
- if: inputs.runner_os == 'ubuntu-latest' && always() && needs.azure-setup.result == 'success'
+ 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 }}
@@ -337,7 +529,7 @@ jobs:
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' }}
+ EXP: ${{ needs.azure-setup.outputs.EXP_ENABLED }}
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 }}
@@ -346,7 +538,7 @@ jobs:
deploy-windows:
name: Deploy on Windows
needs: azure-setup
- if: inputs.runner_os == 'windows-latest' && always() && needs.azure-setup.result == 'success'
+ 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 }}
@@ -355,7 +547,7 @@ jobs:
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' }}
+ EXP: ${{ needs.azure-setup.outputs.EXP_ENABLED }}
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 }}
diff --git a/.github/workflows/job-docker-build.yml b/.github/workflows/job-docker-build.yml
index 62956a437..fc564ea3f 100644
--- a/.github/workflows/job-docker-build.yml
+++ b/.github/workflows/job-docker-build.yml
@@ -19,7 +19,10 @@ on:
env:
BRANCH_NAME: ${{ github.event.workflow_run.head_branch || github.head_ref || github.ref_name }}
-
+permissions:
+ contents: read
+ actions: read
+
jobs:
docker-build:
if: inputs.trigger_type == 'workflow_dispatch' && inputs.build_docker_image == true
diff --git a/.github/workflows/job-send-notification.yml b/.github/workflows/job-send-notification.yml
index 87baad34f..e5c833a33 100644
--- a/.github/workflows/job-send-notification.yml
+++ b/.github/workflows/job-send-notification.yml
@@ -67,7 +67,10 @@ 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' }}
-
+permissions:
+ contents: read
+ actions: read
+
jobs:
send-notification:
runs-on: ubuntu-latest
@@ -75,18 +78,176 @@ jobs:
env:
accelerator_name: "DocGen"
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_WEB_APPURL: ${{ inputs.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 WEB_APPURL (must start with https if provided)
+ if [[ -n "$INPUT_WEB_APPURL" ]]; then
+ if [[ ! "$INPUT_WEB_APPURL" =~ ^https:// ]]; then
+ echo "❌ ERROR: WEB_APPURL must start with 'https://', got: '$INPUT_WEB_APPURL'"
+ VALIDATION_FAILED=true
+ else
+ echo "✅ WEB_APPURL: '$INPUT_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
+ env:
+ RUN_E2E_TESTS_INPUT: ${{ inputs.run_e2e_tests }}
run: |
- if [ "${{ env.RUN_E2E_TESTS }}" = "GoldenPath-Testing" ]; then
+ if [ "$RUN_E2E_TESTS_INPUT" = "GoldenPath-Testing" ]; then
TEST_SUITE_NAME="Golden Path Testing"
- elif [ "${{ env.RUN_E2E_TESTS }}" = "Smoke-Testing" ]; then
+ elif [ "$RUN_E2E_TESTS_INPUT" = "Smoke-Testing" ]; then
TEST_SUITE_NAME="Smoke Testing"
- elif [ "${{ env.RUN_E2E_TESTS }}" = "None" ]; then
+ elif [ "$RUN_E2E_TESTS_INPUT" = "None" ]; then
TEST_SUITE_NAME="None"
else
- TEST_SUITE_NAME="${{ env.RUN_E2E_TESTS }}"
+ TEST_SUITE_NAME="$RUN_E2E_TESTS_INPUT"
fi
echo "TEST_SUITE_NAME=$TEST_SUITE_NAME" >> $GITHUB_OUTPUT
echo "Test Suite: $TEST_SUITE_NAME"
@@ -94,6 +255,9 @@ jobs:
- name: Send Quota Failure Notification
if: inputs.deploy_result == 'failure' && inputs.QUOTA_FAILED == 'true'
shell: bash
+ env:
+ INPUT_DEPLOY_RESULT: ${{ inputs.deploy_result }}
+ INPUT_QUOTA_FAILED: ${{ inputs.QUOTA_FAILED }}
run: |
RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
EMAIL_BODY=$(cat <
We would like to inform you that the ${{ env.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: ${{ env.WAF_ENABLED }}
• EXP Enabled: ${{ env.EXP }}
Run URL: ${RUN_URL}
Best regards,
Your Automation Team
The ${{ env.accelerator_name }} pipeline executed against the existing 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
Dear Team,
The ${{ env.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
The ${{ env.accelerator_name }} pipeline executed against the existing 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
Dear Team,
The ${{ env.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