From 77b46fc1be6e12f42254061b14a3ce39de40da93 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Fri, 9 Jan 2026 11:50:53 +0530 Subject: [PATCH 01/10] Add validation step for Inputs and Map Inputs to env --- .github/workflows/job-cleanup-deployment.yml | 146 +++++++++ .github/workflows/job-deploy-linux.yml | 201 ++++++++++-- .github/workflows/job-deploy-windows.yml | 215 +++++++++++-- .github/workflows/job-deploy.yml | 234 ++++++++++++-- .github/workflows/job-send-notification.yml | 310 ++++++++++++++++--- 5 files changed, 992 insertions(+), 114 deletions(-) diff --git a/.github/workflows/job-cleanup-deployment.yml b/.github/workflows/job-cleanup-deployment.yml index 6b920a4e..a53cc82f 100644 --- a/.github/workflows/job-cleanup-deployment.yml +++ b/.github/workflows/job-cleanup-deployment.yml @@ -52,6 +52,152 @@ jobs: ENV_NAME: ${{ inputs.ENV_NAME }} IMAGE_TAG: ${{ inputs.IMAGE_TAG }} steps: + - name: Validate Workflow Input Parameters + shell: bash + env: + INPUT_RUNNER_OS: ${{ inputs.runner_os }} + INPUT_TRIGGER_TYPE: ${{ inputs.trigger_type }} + INPUT_CLEANUP_RESOURCES: ${{ inputs.cleanup_resources }} + INPUT_EXISTING_WEBAPP_URL: ${{ inputs.existing_webapp_url }} + INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }} + INPUT_AZURE_LOCATION: ${{ inputs.AZURE_LOCATION }} + INPUT_AZURE_ENV_OPENAI_LOCATION: ${{ inputs.AZURE_ENV_OPENAI_LOCATION }} + INPUT_ENV_NAME: ${{ inputs.ENV_NAME }} + INPUT_IMAGE_TAG: ${{ inputs.IMAGE_TAG }} + run: | + echo "🔍 Validating workflow input parameters..." + VALIDATION_FAILED=false + + # Validate runner_os (required, must be specific values) + if [[ -z "$INPUT_RUNNER_OS" ]]; then + echo "❌ ERROR: runner_os is required but not provided" + VALIDATION_FAILED=true + else + ALLOWED_RUNNER_OS=("ubuntu-latest" "windows-latest") + if [[ ! " ${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 + fi + + # 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 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 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 RESOURCE_GROUP_NAME (required, Azure resource group naming convention) + if [[ -z "$INPUT_RESOURCE_GROUP_NAME" ]]; then + echo "❌ ERROR: RESOURCE_GROUP_NAME is required but not provided" + VALIDATION_FAILED=true + else + 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 AZURE_LOCATION (required) + if [[ -z "$INPUT_AZURE_LOCATION" ]]; then + echo "❌ ERROR: AZURE_LOCATION is required but not provided" + VALIDATION_FAILED=true + else + # Azure location should be lowercase alphanumeric (e.g., eastus, westeurope) + if [[ ! "$INPUT_AZURE_LOCATION" =~ ^[a-z0-9]+$ ]]; then + echo "❌ ERROR: AZURE_LOCATION '$INPUT_AZURE_LOCATION' is invalid. Should be lowercase alphanumeric (e.g., 'eastus', 'westeurope')" + VALIDATION_FAILED=true + else + echo "✅ AZURE_LOCATION: '$INPUT_AZURE_LOCATION' is valid" + fi + fi + + # Validate AZURE_ENV_OPENAI_LOCATION (required) + if [[ -z "$INPUT_AZURE_ENV_OPENAI_LOCATION" ]]; then + echo "❌ ERROR: AZURE_ENV_OPENAI_LOCATION is required but not provided" + VALIDATION_FAILED=true + else + # Azure location should be lowercase alphanumeric + if [[ ! "$INPUT_AZURE_ENV_OPENAI_LOCATION" =~ ^[a-z0-9]+$ ]]; then + echo "❌ ERROR: AZURE_ENV_OPENAI_LOCATION '$INPUT_AZURE_ENV_OPENAI_LOCATION' is invalid. Should be lowercase alphanumeric (e.g., 'eastus', 'westeurope')" + VALIDATION_FAILED=true + else + echo "✅ AZURE_ENV_OPENAI_LOCATION: '$INPUT_AZURE_ENV_OPENAI_LOCATION' is valid" + fi + fi + + # Validate ENV_NAME (required, alphanumeric with hyphens) + if [[ -z "$INPUT_ENV_NAME" ]]; then + echo "❌ ERROR: ENV_NAME is required but not provided" + VALIDATION_FAILED=true + else + if [[ ! "$INPUT_ENV_NAME" =~ ^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$|^[a-zA-Z0-9]$ ]]; then + echo "❌ ERROR: ENV_NAME '$INPUT_ENV_NAME' is invalid. Must start and end with alphanumeric, can contain hyphens" + VALIDATION_FAILED=true + elif [[ ${#INPUT_ENV_NAME} -gt 64 ]]; then + echo "❌ ERROR: ENV_NAME '$INPUT_ENV_NAME' exceeds 64 characters" + VALIDATION_FAILED=true + else + echo "✅ ENV_NAME: '$INPUT_ENV_NAME' is valid" + fi + fi + + # Validate IMAGE_TAG (required) + if [[ -z "$INPUT_IMAGE_TAG" ]]; then + echo "❌ ERROR: IMAGE_TAG is required but not provided" + VALIDATION_FAILED=true + else + # Docker image tag validation (alphanumeric, dots, dashes, underscores, max 128 chars) + if [[ ! "$INPUT_IMAGE_TAG" =~ ^[a-zA-Z0-9._-]+$ ]]; then + echo "❌ ERROR: IMAGE_TAG '$INPUT_IMAGE_TAG' is invalid. Must contain only alphanumerics, dots, dashes, and underscores" + VALIDATION_FAILED=true + elif [[ ${#INPUT_IMAGE_TAG} -gt 128 ]]; then + echo "❌ ERROR: IMAGE_TAG '$INPUT_IMAGE_TAG' exceeds 128 characters" + VALIDATION_FAILED=true + else + echo "✅ IMAGE_TAG: '$INPUT_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: Setup Azure CLI shell: bash run: | diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml index 41c0362b..d78963b0 100644 --- a/.github/workflows/job-deploy-linux.yml +++ b/.github/workflows/job-deploy-linux.yml @@ -47,13 +47,148 @@ jobs: outputs: CONTAINER_WEB_APPURL: ${{ steps.get_output_linux.outputs.CONTAINER_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-]+/resourceGroups/[^/]+/providers/microsoft\.operationalinsights/workspaces/[^/]+$ ]]; then + echo "❌ ERROR: AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID is invalid. Must be a valid Azure Resource ID format:" + echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}" + echo " Got: '$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID'" + VALIDATION_FAILED=true + else + echo "✅ AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: Valid Resource ID format" + fi + fi + + # Validate AZURE_EXISTING_AI_PROJECT_RESOURCE_ID (optional, if provided must be valid Resource ID) + if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then + if [[ ! "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/(Microsoft\.MachineLearningServices/(workspaces|projects)/[^/]+|Microsoft\.CognitiveServices/accounts/[^/]+/projects/[^/]+)$ ]]; then + echo "❌ ERROR: AZURE_EXISTING_AI_PROJECT_RESOURCE_ID is invalid. Must be a valid Azure Resource ID format:" + echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CognitiveServices/accounts/{accountName}/projects/{projectName}" + echo " Got: '$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID'" + VALIDATION_FAILED=true + else + echo "✅ AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: Valid Resource ID format" + fi + fi + + # Fail workflow if any validation failed + if [[ "$VALIDATION_FAILED" == "true" ]]; then + echo "" + echo "❌ Parameter validation failed. Please correct the errors above and try again." + exit 1 + fi + + echo "" + echo "✅ All input parameters validated successfully!" + - name: 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 @@ -83,11 +218,21 @@ 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 }} + 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 }} run: | set -e echo "Starting azd deployment..." - echo "EXP: ${{ inputs.EXP }}" - echo "Using Docker Image Tag: ${{ inputs.IMAGE_TAG }}" + echo "EXP: $EXP" + echo "Using Docker Image Tag: $IMAGE_TAG" # Install azd (Azure Developer CLI) curl -fsSL https://aka.ms/install-azd.sh | bash @@ -96,20 +241,20 @@ jobs: current_date=$(date -u +"%Y-%m-%dT%H:%M:%S.%7NZ") 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 }} # Set additional parameters azd env set AZURE_SUBSCRIPTION_ID="${{ secrets.AZURE_SUBSCRIPTION_ID }}" - azd env set AZURE_ENV_AI_DEPLOYMENTS_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_CONTAINER_IMAGE_TAG="${{ inputs.IMAGE_TAG }}" + azd env set AZURE_ENV_AI_DEPLOYMENTS_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_CONTAINER_IMAGE_TAG="$IMAGE_TAG" - if [[ "${{ inputs.BUILD_DOCKER_IMAGE }}" == "true" ]]; then + if [[ "$BUILD_DOCKER_IMAGE" == "true" ]]; then ACR_NAME=$(echo "${{ secrets.ACR_TEST_LOGIN_SERVER }}") azd env set AZURE_ENV_CONTAINER_REGISTRY_ENDPOINT="$ACR_NAME" echo "Set ACR name to: $ACR_NAME" @@ -117,17 +262,17 @@ jobs: echo "Skipping ACR name configuration (using existing image)" fi - if [[ "${{ inputs.EXP }}" == "true" ]]; then + if [[ "$EXP" == "true" ]]; then echo "✅ EXP ENABLED - Setting EXP parameters..." - if [[ -n "${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}" ]]; then - EXP_LOG_ANALYTICS_ID="${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}" + if [[ -n "$AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then + EXP_LOG_ANALYTICS_ID="$AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" else EXP_LOG_ANALYTICS_ID="${{ secrets.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}" fi - if [[ -n "${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" ]]; then - EXP_AI_PROJECT_ID="${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" + if [[ -n "$AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then + EXP_AI_PROJECT_ID="$AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" else EXP_AI_PROJECT_ID="${{ secrets.AZURE_ENV_FOUNDRY_PROJECT_ID }}" fi @@ -189,33 +334,45 @@ jobs: - name: Disable Auth in Web App shell: bash + env: + RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }} run: | az containerapp update --name ${{ env.CONTAINER_WEB_APPNAME }} \ - --resource-group ${{ inputs.RESOURCE_GROUP_NAME }} \ + --resource-group $RESOURCE_GROUP_NAME \ --set-env-vars APP_AUTH_ENABLED=false - name: Disable Auth in API App shell: bash + env: + RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }} run: | sleep 30 az containerapp update --name ${{ env.CONTAINER_API_APPNAME }} \ - --resource-group ${{ inputs.RESOURCE_GROUP_NAME }} \ + --resource-group $RESOURCE_GROUP_NAME \ --set-env-vars APP_AUTH_ENABLED=false - name: Generate Deployment 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 }} + CONFIG_TYPE: ${{ inputs.WAF_ENABLED == 'true' && inputs.EXP == 'true' && 'WAF + EXP' || inputs.WAF_ENABLED == 'true' && inputs.EXP != 'true' && 'WAF + Non-EXP' || inputs.WAF_ENABLED != 'true' && inputs.EXP == 'true' && 'Non-WAF + EXP' || 'Non-WAF + Non-EXP' }} run: | echo "## 🚀 Deploy Job Summary (Linux)" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY echo "| **Job Status** | ${{ job.status == 'success' && '✅ Success' || '❌ Failed' }} |" >> $GITHUB_STEP_SUMMARY - echo "| **Resource Group** | \`${{ 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 + echo "| **Resource Group** | \`$RESOURCE_GROUP_NAME\` |" >> $GITHUB_STEP_SUMMARY + 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 echo "### ✅ Deployment Details" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml index 4fc84f87..660e7369 100644 --- a/.github/workflows/job-deploy-windows.yml +++ b/.github/workflows/job-deploy-windows.yml @@ -47,13 +47,148 @@ jobs: outputs: CONTAINER_WEB_APPURL: ${{ steps.get_output_windows.outputs.CONTAINER_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-]+/resourceGroups/[^/]+/providers/microsoft\.operationalinsights/workspaces/[^/]+$ ]]; then + echo "❌ ERROR: AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID is invalid. Must be a valid Azure Resource ID format:" + echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}" + echo " Got: '$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID'" + VALIDATION_FAILED=true + else + echo "✅ AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: Valid Resource ID format" + fi + fi + + # Validate AZURE_EXISTING_AI_PROJECT_RESOURCE_ID (optional, if provided must be valid Resource ID) + if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then + if [[ ! "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/(Microsoft\.MachineLearningServices/(workspaces|projects)/[^/]+|Microsoft\.CognitiveServices/accounts/[^/]+/projects/[^/]+)$ ]]; then + echo "❌ ERROR: AZURE_EXISTING_AI_PROJECT_RESOURCE_ID is invalid. Must be a valid Azure Resource ID format:" + echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CognitiveServices/accounts/{accountName}/projects/{projectName}" + echo " Got: '$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID'" + VALIDATION_FAILED=true + else + echo "✅ AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: Valid Resource ID format" + fi + fi + + # Fail workflow if any validation failed + if [[ "$VALIDATION_FAILED" == "true" ]]; then + echo "" + echo "❌ Parameter validation failed. Please correct the errors above and try again." + exit 1 + fi + + echo "" + echo "✅ All input parameters validated successfully!" + - name: 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,28 +210,38 @@ 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 }} + 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 }} run: | $ErrorActionPreference = "Stop" Write-Host "Starting azd deployment..." - Write-Host "EXP: ${{ inputs.EXP }}" - Write-Host "Using Docker Image Tag: ${{ inputs.IMAGE_TAG }}" + Write-Host "EXP: $env:EXP" + Write-Host "Using Docker Image Tag: $env:IMAGE_TAG" 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_AI_DEPLOYMENTS_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_CONTAINER_IMAGE_TAG="${{ inputs.IMAGE_TAG }}" + azd env set AZURE_ENV_AI_DEPLOYMENTS_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_CONTAINER_IMAGE_TAG="$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") { # Extract ACR name from login server and set as environment variable $ACR_NAME = "${{ secrets.ACR_TEST_LOGIN_SERVER }}" azd env set AZURE_ENV_CONTAINER_REGISTRY_ENDPOINT="$ACR_NAME" @@ -105,18 +250,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:AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID -ne "") { + $EXP_LOG_ANALYTICS_ID = $env: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:AZURE_EXISTING_AI_PROJECT_RESOURCE_ID -ne "") { + $EXP_AI_PROJECT_ID = $env:AZURE_EXISTING_AI_PROJECT_RESOURCE_ID } else { $EXP_AI_PROJECT_ID = "${{ secrets.AZURE_ENV_FOUNDRY_PROJECT_ID }}" } @@ -178,35 +323,59 @@ jobs: - name: Disable Auth in Web App shell: bash + env: + RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }} run: | az containerapp update --name ${{ env.CONTAINER_WEB_APPNAME }} \ - --resource-group ${{ inputs.RESOURCE_GROUP_NAME }} \ + --resource-group $RESOURCE_GROUP_NAME \ --set-env-vars APP_AUTH_ENABLED=false - name: Disable Auth in API App shell: bash + env: + RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }} run: | sleep 30 az containerapp update --name ${{ env.CONTAINER_API_APPNAME }} \ - --resource-group ${{ inputs.RESOURCE_GROUP_NAME }} \ + --resource-group $RESOURCE_GROUP_NAME \ --set-env-vars APP_AUTH_ENABLED=false - name: Generate Deployment Summary if: always() shell: bash + env: + RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }} + AZURE_LOCATION: ${{ inputs.AZURE_LOCATION }} + AZURE_ENV_OPENAI_LOCATION: ${{ inputs.AZURE_ENV_OPENAI_LOCATION }} + IMAGE_TAG: ${{ inputs.IMAGE_TAG }} + WAF_ENABLED: ${{ inputs.WAF_ENABLED }} + EXP: ${{ inputs.EXP }} + JOB_STATUS: ${{ job.status }} run: | echo "## 🚀 Deploy Job Summary (Windows)" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY echo "| **Job Status** | ${{ job.status == 'success' && '✅ Success' || '❌ Failed' }} |" >> $GITHUB_STEP_SUMMARY - 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 "| **Resource Group** | \`${{ inputs.RESOURCE_GROUP_NAME }}\` |" >> $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 + + # 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 "| **Resource Group** | \`$RESOURCE_GROUP_NAME\` |" >> $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 "- **Container Web App URL**: [${{ env.CONTAINER_WEB_APPURL }}](${{ env.CONTAINER_WEB_APPURL }})" >> $GITHUB_STEP_SUMMARY echo "- **Container API App URL**: [${{ env.CONTAINER_API_APPURL }}](${{ env.CONTAINER_API_APPURL }})" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/job-deploy.yml b/.github/workflows/job-deploy.yml index 1d9c3a5e..e1642c42 100644 --- a/.github/workflows/job-deploy.yml +++ b/.github/workflows/job-deploy.yml @@ -112,18 +112,191 @@ jobs: QUOTA_FAILED: ${{ steps.quota_failure_output.outputs.QUOTA_FAILED }} 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-]+/resourceGroups/[^/]+/providers/microsoft\.operationalinsights/workspaces/[^/]+$ ]]; then + echo "❌ ERROR: AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID is invalid. Must be a valid Azure Resource ID format:" + echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}" + echo " Got: '$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID'" + VALIDATION_FAILED=true + else + echo "✅ AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: Valid Resource ID format" + fi + fi + + # Validate AZURE_EXISTING_AI_PROJECT_RESOURCE_ID (Azure Resource ID format) + if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then + if [[ ! "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/(Microsoft\.MachineLearningServices/(workspaces|projects)/[^/]+|Microsoft\.CognitiveServices/accounts/[^/]+/projects/[^/]+)$ ]]; then + echo "❌ ERROR: AZURE_EXISTING_AI_PROJECT_RESOURCE_ID is invalid. Must be a valid Azure Resource ID format:" + echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CognitiveServices/accounts/{accountName}/projects/{projectName}" + echo " Got: '$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID'" + VALIDATION_FAILED=true + else + echo "✅ AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: Valid Resource ID format" + fi + fi + + # Validate 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 shell: bash + env: + INPUT_EXP: ${{ inputs.EXP }} + INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }} + INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }} run: | echo "🔍 Validating EXP configuration..." - if [[ "${{ inputs.EXP }}" != "true" ]]; then - if [[ -n "${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}" ]] || [[ -n "${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" ]]; then + if [[ "$INPUT_EXP" != "true" ]]; then + if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]] || [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then echo "🔧 AUTO-ENABLING EXP: EXP parameter values were provided but EXP was not explicitly enabled." echo "" echo "You provided values for:" - [[ -n "${{ 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 }}'" + [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]] && echo " - Azure Log Analytics Workspace ID: '$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID'" + [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]] && echo " - Azure AI Project Resource ID: '$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID'" echo "" echo "✅ Automatically enabling EXP to use these values." echo "EXP=true" >> $GITHUB_ENV @@ -175,13 +348,16 @@ jobs: - name: Set Deployment Region id: set_region shell: bash + env: + INPUT_TRIGGER_TYPE: ${{ inputs.trigger_type }} + INPUT_AZURE_LOCATION: ${{ inputs.azure_location }} run: | echo "Selected Region from Quota Check: $VALID_REGION" echo "AZURE_ENV_OPENAI_LOCATION=$VALID_REGION" >> $GITHUB_ENV echo "AZURE_ENV_OPENAI_LOCATION=$VALID_REGION" >> $GITHUB_OUTPUT - if [[ "${{ inputs.trigger_type }}" == "workflow_dispatch" && -n "${{ inputs.azure_location }}" ]]; then - USER_SELECTED_LOCATION="${{ inputs.azure_location }}" + if [[ "$INPUT_TRIGGER_TYPE" == "workflow_dispatch" && -n "$INPUT_AZURE_LOCATION" ]]; then + USER_SELECTED_LOCATION="$INPUT_AZURE_LOCATION" echo "Using user-selected Azure location: $USER_SELECTED_LOCATION" echo "AZURE_LOCATION=$USER_SELECTED_LOCATION" >> $GITHUB_ENV echo "AZURE_LOCATION=$USER_SELECTED_LOCATION" >> $GITHUB_OUTPUT @@ -194,11 +370,13 @@ 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="cp" # Account name as specified @@ -296,33 +474,43 @@ jobs: - name: Display Workflow Configuration to GitHub Summary shell: bash + env: + INPUT_TRIGGER_TYPE: ${{ inputs.trigger_type }} + INPUT_RUNNER_OS: ${{ inputs.runner_os }} + INPUT_AZURE_LOCATION: ${{ inputs.azure_location }} + INPUT_RESOURCE_GROUP_NAME: ${{ inputs.resource_group_name }} + EVENT_NAME: ${{ github.event_name }} + WAF_STATUS: ${{ env.WAF_ENABLED == 'true' && '✅ Yes' || '❌ No' }} + EXP_STATUS: ${{ env.EXP == 'true' && '✅ Yes' || '❌ No' }} + CLEANUP_STATUS: ${{ env.CLEANUP_RESOURCES == 'true' && '✅ Yes' || '❌ No' }} + BUILD_DOCKER_STATUS: ${{ env.BUILD_DOCKER_IMAGE == 'true' && '✅ Yes' || '❌ No' }} 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 "| **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 + echo "| **Trigger Type** | \`$EVENT_NAME\` |" >> $GITHUB_STEP_SUMMARY + echo "| **Branch** | \`$BRANCH_NAME\` |" >> $GITHUB_STEP_SUMMARY + echo "| **Runner OS** | \`$INPUT_RUNNER_OS\` |" >> $GITHUB_STEP_SUMMARY + echo "| **WAF Enabled** | $WAF_STATUS |" >> $GITHUB_STEP_SUMMARY + echo "| **EXP Enabled** | $EXP_STATUS |" >> $GITHUB_STEP_SUMMARY + echo "| **Run E2E Tests** | \`$RUN_E2E_TESTS\` |" >> $GITHUB_STEP_SUMMARY + echo "| **Cleanup Resources** | $CLEANUP_STATUS |" >> $GITHUB_STEP_SUMMARY + echo "| **Build Docker Image** | $BUILD_DOCKER_STATUS |" >> $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 [[ "$INPUT_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 + echo "| **Resource Group** | \`$RESOURCE_GROUP_NAME\` (Auto-generated) |" >> $GITHUB_STEP_SUMMARY fi echo "" >> $GITHUB_STEP_SUMMARY - if [[ "${{ inputs.trigger_type }}" != "workflow_dispatch" ]]; then + if [[ "$INPUT_TRIGGER_TYPE" != "workflow_dispatch" ]]; then echo "â„šī¸ **Note:** Automatic Trigger - Using Non-WAF + Non-EXP configuration" >> $GITHUB_STEP_SUMMARY else echo "â„šī¸ **Note:** Manual Trigger - Using user-specified configuration" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/job-send-notification.yml b/.github/workflows/job-send-notification.yml index fe971270..0dd920c0 100644 --- a/.github/workflows/job-send-notification.yml +++ b/.github/workflows/job-send-notification.yml @@ -75,18 +75,176 @@ 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' or 'false') + if [[ "$INPUT_QUOTA_FAILED" != "true" && "$INPUT_QUOTA_FAILED" != "false" ]]; then + echo "❌ ERROR: QUOTA_FAILED must be 'true' or 'false', got: '$INPUT_QUOTA_FAILED'" + VALIDATION_FAILED=true + else + echo "✅ QUOTA_FAILED: '$INPUT_QUOTA_FAILED' is valid" + fi + + # Validate TEST_SUCCESS (must be 'true' or 'false' or empty) + if [[ -n "$INPUT_TEST_SUCCESS" ]]; then + if [[ "$INPUT_TEST_SUCCESS" != "true" && "$INPUT_TEST_SUCCESS" != "false" ]]; then + echo "❌ ERROR: TEST_SUCCESS must be 'true', 'false', or empty, got: '$INPUT_TEST_SUCCESS'" + VALIDATION_FAILED=true + else + echo "✅ TEST_SUCCESS: '$INPUT_TEST_SUCCESS' is valid" + fi + fi + + # Validate TEST_REPORT_URL (must start with https if provided) + if [[ -n "$INPUT_TEST_REPORT_URL" ]]; then + if [[ ! "$INPUT_TEST_REPORT_URL" =~ ^https:// ]]; then + echo "❌ ERROR: TEST_REPORT_URL must start with 'https://', got: '$INPUT_TEST_REPORT_URL'" + VALIDATION_FAILED=true + else + echo "✅ TEST_REPORT_URL: '$INPUT_TEST_REPORT_URL' is valid" + fi + fi + + # Fail workflow if any validation failed + if [[ "$VALIDATION_FAILED" == "true" ]]; then + echo "" + echo "❌ Parameter validation failed. Please correct the errors above and try again." + exit 1 + fi + + echo "" + echo "✅ All input parameters validated successfully!" + - name: Determine Test Suite Display Name id: test_suite shell: bash + env: + RUN_E2E_TESTS: ${{ env.RUN_E2E_TESTS }} run: | - if [ "${{ env.RUN_E2E_TESTS }}" = "GoldenPath-Testing" ]; then + if [ "$RUN_E2E_TESTS" = "GoldenPath-Testing" ]; then TEST_SUITE_NAME="Golden Path Testing" - elif [ "${{ env.RUN_E2E_TESTS }}" = "Smoke-Testing" ]; then + elif [ "$RUN_E2E_TESTS" = "Smoke-Testing" ]; then TEST_SUITE_NAME="Smoke Testing" - elif [ "${{ env.RUN_E2E_TESTS }}" = "None" ]; then + elif [ "$RUN_E2E_TESTS" = "None" ]; then TEST_SUITE_NAME="None" else - TEST_SUITE_NAME="${{ env.RUN_E2E_TESTS }}" + TEST_SUITE_NAME="$RUN_E2E_TESTS" fi echo "TEST_SUITE_NAME=$TEST_SUITE_NAME" >> $GITHUB_OUTPUT echo "Test Suite: $TEST_SUITE_NAME" @@ -94,131 +252,191 @@ jobs: - 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 }} + LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }} run: | - RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + 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 ${{ env.accelerator_name }} deployment has failed due to insufficient quota in the requested regions.

Issue Details:
â€ĸ Quota check failed for GPT model
â€ĸ Required GPT Capacity: ${{ env.GPT_MIN_CAPACITY }}
â€ĸ Checked Regions: ${{ vars.AZURE_REGIONS }}

Run URL: ${RUN_URL}

Please resolve the quota issue and retry the deployment.

Best regards,
Your Automation Team

", - "subject": "${{ env.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 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)" } EOF ) - curl -X POST "${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}" \ + curl -X POST "${LOGICAPP_URL}" \ -H "Content-Type: application/json" \ -d "$EMAIL_BODY" || echo "Failed to send quota failure notification" - name: Send Deployment Failure Notification if: inputs.deploy_result == 'failure' && inputs.QUOTA_FAILED != 'true' shell: bash + env: + DEPLOY_RESULT: ${{ inputs.deploy_result }} + QUOTA_FAILED: ${{ inputs.QUOTA_FAILED }} + 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 }} run: | - RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - RESOURCE_GROUP="${{ inputs.RESOURCE_GROUP_NAME }}" + RUN_URL="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" + RESOURCE_GROUP="${RESOURCE_GROUP_NAME}" EMAIL_BODY=$(cat <Dear Team,

We would like to inform you that the ${{ env.accelerator_name }} deployment process has encountered an issue and has failed to complete successfully.

Deployment Details:
â€ĸ Resource Group: ${RESOURCE_GROUP}
â€ĸ WAF Enabled: ${{ env.WAF_ENABLED }}
â€ĸ EXP Enabled: ${{ env.EXP }}

Run URL: ${RUN_URL}

Please investigate the deployment failure at your earliest convenience.

Best regards,
Your Automation Team

", - "subject": "${{ env.accelerator_name }} Pipeline - Failed" + "body": "

Dear Team,

We would like to inform you that the ${ACCELERATOR_NAME} deployment process has encountered an issue and has failed to complete successfully.

Deployment Details:
â€ĸ Resource Group: ${RESOURCE_GROUP}
â€ĸ WAF Enabled: ${WAF_ENABLED}
â€ĸ EXP Enabled: ${EXP}

Run URL: ${RUN_URL}

Please investigate the deployment failure at your earliest convenience.

Best regards,
Your Automation Team

", + "subject": "${ACCELERATOR_NAME} Pipeline - Failed" } EOF ) - curl -X POST "${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}" \ + curl -X POST "${LOGICAPP_URL}" \ -H "Content-Type: application/json" \ -d "$EMAIL_BODY" || echo "Failed to send deployment failure notification" - name: Send Success Notification if: inputs.deploy_result == 'success' && (inputs.e2e_test_result == 'skipped' || inputs.TEST_SUCCESS == 'true') shell: bash + env: + 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 }} + 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 }} run: | - RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - WEBAPP_URL="${{ inputs.CONTAINER_WEB_APPURL || inputs.existing_webapp_url }}" - RESOURCE_GROUP="${{ inputs.RESOURCE_GROUP_NAME }}" - TEST_REPORT_URL="${{ inputs.TEST_REPORT_URL }}" - TEST_SUITE_NAME="${{ steps.test_suite.outputs.TEST_SUITE_NAME }}" + 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}" - if [ "${{ inputs.e2e_test_result }}" = "skipped" ]; then + if [ "${E2E_TEST_RESULT}" = "skipped" ]; then EMAIL_BODY=$(cat <Dear Team,

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

", - "subject": "${{ env.accelerator_name }} Pipeline - Deployment Success" + "body": "

Dear Team,

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

Deployment Details:
â€ĸ Resource Group: ${RESOURCE_GROUP}
â€ĸ Web App URL: ${WEBAPP_URL}
â€ĸ E2E Tests: Skipped (as configured)

Configuration:
â€ĸ WAF Enabled: ${WAF_ENABLED}
â€ĸ EXP Enabled: ${EXP}

Run URL: ${RUN_URL}

Best regards,
Your Automation Team

", + "subject": "${ACCELERATOR_NAME} Pipeline - Deployment Success" } EOF ) else EMAIL_BODY=$(cat <Dear Team,

We would like to inform you that the ${{ env.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: ${{ env.WAF_ENABLED }}
â€ĸ EXP Enabled: ${{ env.EXP }}

Run URL: ${RUN_URL}

Best regards,
Your Automation Team

", - "subject": "${{ env.accelerator_name }} Pipeline - Test Automation - Success" + "body": "

Dear Team,

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

Deployment Details:
â€ĸ Resource Group: ${RESOURCE_GROUP}
â€ĸ Web App URL: ${WEBAPP_URL}
â€ĸ E2E Tests: Passed ✅
â€ĸ Test Suite: ${TEST_SUITE_NAME}
â€ĸ Test Report: View Report

Configuration:
â€ĸ WAF Enabled: ${WAF_ENABLED}
â€ĸ EXP Enabled: ${EXP}

Run URL: ${RUN_URL}

Best regards,
Your Automation Team

", + "subject": "${ACCELERATOR_NAME} Pipeline - Test Automation - Success" } EOF ) fi - curl -X POST "${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}" \ + curl -X POST "${LOGICAPP_URL}" \ -H "Content-Type: application/json" \ -d "$EMAIL_BODY" || echo "Failed to send success notification" - name: Send Test Failure Notification if: inputs.deploy_result == 'success' && inputs.e2e_test_result != 'skipped' && inputs.TEST_SUCCESS != 'true' shell: bash + env: + 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 }} + LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }} run: | - RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - TEST_REPORT_URL="${{ inputs.TEST_REPORT_URL }}" - WEBAPP_URL="${{ inputs.CONTAINER_WEB_APPURL || inputs.existing_webapp_url }}" - RESOURCE_GROUP="${{ inputs.RESOURCE_GROUP_NAME }}" - TEST_SUITE_NAME="${{ steps.test_suite.outputs.TEST_SUITE_NAME }}" + 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}" EMAIL_BODY=$(cat <Dear Team,

We would like to inform you that ${{ env.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": "${{ env.accelerator_name }} Pipeline - Test Automation - Failed" + "body": "

Dear Team,

We would like to inform you that ${ACCELERATOR_NAME} accelerator test automation process has encountered issues and failed to complete successfully.

Deployment Details:
â€ĸ Resource Group: ${RESOURCE_GROUP}
â€ĸ Web App URL: ${WEBAPP_URL}
â€ĸ Deployment Status: ✅ Success
â€ĸ E2E Tests: ❌ Failed
â€ĸ Test Suite: ${TEST_SUITE_NAME}

Test Details:
â€ĸ Test Report: View Report

Run URL: ${RUN_URL}

Please investigate the matter at your earliest convenience.

Best regards,
Your Automation Team

", + "subject": "${ACCELERATOR_NAME} Pipeline - Test Automation - Failed" } EOF ) - curl -X POST "${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}" \ + curl -X POST "${LOGICAPP_URL}" \ -H "Content-Type: application/json" \ -d "$EMAIL_BODY" || echo "Failed to send test failure notification" - name: Send Existing URL Success Notification if: inputs.deploy_result == 'skipped' && inputs.existing_webapp_url != '' && inputs.e2e_test_result == 'success' && (inputs.TEST_SUCCESS == 'true' || inputs.TEST_SUCCESS == '') shell: bash + env: + 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 }} + LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }} run: | - RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - EXISTING_URL="${{ inputs.existing_webapp_url }}" - TEST_REPORT_URL="${{ inputs.TEST_REPORT_URL }}" - TEST_SUITE_NAME="${{ steps.test_suite.outputs.TEST_SUITE_NAME }}" + RUN_URL="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" + EXISTING_URL="${EXISTING_WEBAPP_URL}" EMAIL_BODY=$(cat <Dear 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

", - "subject": "${{ env.accelerator_name }} Pipeline - Test Automation Passed (Existing URL)" + "body": "

Dear Team,

The ${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

", + "subject": "${ACCELERATOR_NAME} Pipeline - Test Automation Passed (Existing URL)" } EOF ) - curl -X POST "${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}" \ + curl -X POST "${LOGICAPP_URL}" \ -H "Content-Type: application/json" \ -d "$EMAIL_BODY" || echo "Failed to send existing URL success notification" - name: Send Existing URL Test Failure Notification if: inputs.deploy_result == 'skipped' && inputs.existing_webapp_url != '' && inputs.e2e_test_result == 'failure' shell: bash + env: + 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 }} + LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }} run: | - RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - EXISTING_URL="${{ inputs.existing_webapp_url }}" - TEST_REPORT_URL="${{ inputs.TEST_REPORT_URL }}" - TEST_SUITE_NAME="${{ steps.test_suite.outputs.TEST_SUITE_NAME }}" + RUN_URL="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" + EXISTING_URL="${EXISTING_WEBAPP_URL}" EMAIL_BODY=$(cat <Dear 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

", - "subject": "${{ env.accelerator_name }} Pipeline - Test Automation Failed (Existing URL)" + "body": "

Dear Team,

The ${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

", + "subject": "${ACCELERATOR_NAME} Pipeline - Test Automation Failed (Existing URL)" } EOF ) - curl -X POST "${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}" \ + curl -X POST "${LOGICAPP_URL}" \ -H "Content-Type: application/json" \ -d "$EMAIL_BODY" || echo "Failed to send existing URL test failure notification" From 5781e126bdc98cebb5f6c1925b0bbe19125f7536 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Fri, 9 Jan 2026 11:56:16 +0530 Subject: [PATCH 02/10] Add Permissions --- .github/workflows/build-docker-image.yml | 4 ++++ .github/workflows/deploy-linux.yml | 4 +++- .github/workflows/deploy-orchestrator.yml | 4 +++- .github/workflows/deploy-windows.yml | 4 +++- .github/workflows/deploy.yml | 4 +++- .github/workflows/job-cleanup-deployment.yml | 4 +++- .github/workflows/job-deploy-linux.yml | 4 +++- .github/workflows/job-deploy-windows.yml | 4 +++- .github/workflows/job-deploy.yml | 4 +++- .github/workflows/job-docker-build.yml | 4 +++- .github/workflows/job-send-notification.yml | 4 +++- .github/workflows/pylint.yml | 4 +++- .github/workflows/test-automation-v2.yml | 4 +++- .github/workflows/test-automation.yml | 4 +++- .github/workflows/test.yml | 4 +++- 15 files changed, 46 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index edeabddb..eef429e6 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -45,6 +45,10 @@ on: - '.github/workflows/build-docker-image.yml' workflow_dispatch: +permissions: + contents: read + actions: read + jobs: build-and-push: runs-on: ubuntu-latest diff --git a/.github/workflows/deploy-linux.yml b/.github/workflows/deploy-linux.yml index e863b2e5..60c025a2 100644 --- a/.github/workflows/deploy-linux.yml +++ b/.github/workflows/deploy-linux.yml @@ -92,7 +92,9 @@ on: schedule: - cron: '0 9,21 * * *' # Runs at 9:00 AM and 9:00 PM GMT - +permissions: + contents: read + actions: read jobs: Run: uses: ./.github/workflows/deploy-orchestrator.yml diff --git a/.github/workflows/deploy-orchestrator.yml b/.github/workflows/deploy-orchestrator.yml index a86d1116..6eca9baa 100644 --- a/.github/workflows/deploy-orchestrator.yml +++ b/.github/workflows/deploy-orchestrator.yml @@ -64,7 +64,9 @@ 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 diff --git a/.github/workflows/deploy-windows.yml b/.github/workflows/deploy-windows.yml index 6fdc223b..7c4d9d8a 100644 --- a/.github/workflows/deploy-windows.yml +++ b/.github/workflows/deploy-windows.yml @@ -75,7 +75,9 @@ on: # schedule: # - cron: '0 9,21 * * *' # Runs at 9:00 AM and 9:00 PM GMT - +permissions: + contents: read + actions: read jobs: Run: uses: ./.github/workflows/deploy-orchestrator.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index aa5d63d0..200c35e4 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -14,7 +14,9 @@ on: schedule: - cron: "0 9,21 * * *" # Runs at 9:00 AM and 9:00 PM GMT workflow_dispatch: - +permissions: + contents: read + actions: read jobs: deploy: runs-on: ubuntu-latest diff --git a/.github/workflows/job-cleanup-deployment.yml b/.github/workflows/job-cleanup-deployment.yml index a53cc82f..f4df28f5 100644 --- a/.github/workflows/job-cleanup-deployment.yml +++ b/.github/workflows/job-cleanup-deployment.yml @@ -40,7 +40,9 @@ on: description: 'Docker Image Tag' required: true type: string - +permissions: + contents: read + actions: read jobs: cleanup-deployment: runs-on: ${{ inputs.runner_os }} diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml index d78963b0..791af146 100644 --- a/.github/workflows/job-deploy-linux.yml +++ b/.github/workflows/job-deploy-linux.yml @@ -38,7 +38,9 @@ on: CONTAINER_WEB_APPURL: description: "Container Web App URL" value: ${{ jobs.deploy-linux.outputs.CONTAINER_WEB_APPURL }} - +permissions: + contents: read + actions: read jobs: deploy-linux: runs-on: ubuntu-latest diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml index 660e7369..3573c8f3 100644 --- a/.github/workflows/job-deploy-windows.yml +++ b/.github/workflows/job-deploy-windows.yml @@ -38,7 +38,9 @@ on: CONTAINER_WEB_APPURL: description: "Container Web App URL" value: ${{ jobs.deploy-windows.outputs.CONTAINER_WEB_APPURL }} - +permissions: + contents: read + actions: read jobs: deploy-windows: runs-on: windows-latest diff --git a/.github/workflows/job-deploy.yml b/.github/workflows/job-deploy.yml index e1642c42..6af0cb1a 100644 --- a/.github/workflows/job-deploy.yml +++ b/.github/workflows/job-deploy.yml @@ -97,7 +97,9 @@ 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 diff --git a/.github/workflows/job-docker-build.yml b/.github/workflows/job-docker-build.yml index 316b65e3..185ac7e8 100644 --- a/.github/workflows/job-docker-build.yml +++ b/.github/workflows/job-docker-build.yml @@ -19,7 +19,9 @@ 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 0dd920c0..ff8e3f13 100644 --- a/.github/workflows/job-send-notification.yml +++ b/.github/workflows/job-send-notification.yml @@ -67,7 +67,9 @@ 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 diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 0411a85c..638fda67 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -9,7 +9,9 @@ on: - 'src/**/requirements.txt' - 'src/**/pyproject.toml' - '.github/workflows/pylint.yml' - +permissions: + contents: read + actions: read jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/test-automation-v2.yml b/.github/workflows/test-automation-v2.yml index e0a33d02..4ec41a0b 100644 --- a/.github/workflows/test-automation-v2.yml +++ b/.github/workflows/test-automation-v2.yml @@ -24,7 +24,9 @@ env: url: ${{ inputs.CP_WEB_URL }} accelerator_name: "Content Processing" test_suite: ${{ inputs.TEST_SUITE }} - +permissions: + contents: read + actions: read jobs: test: runs-on: ubuntu-latest diff --git a/.github/workflows/test-automation.yml b/.github/workflows/test-automation.yml index 1790e625..1112a225 100644 --- a/.github/workflows/test-automation.yml +++ b/.github/workflows/test-automation.yml @@ -14,7 +14,9 @@ env: url: ${{ inputs.CP_WEB_URL }} CP_RG: ${{ inputs.CP_RG }} accelerator_name: "Content Processing" - +permissions: + contents: read + actions: read jobs: test: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 390db316..dd375649 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,7 +32,9 @@ on: - 'src/**/pytest.ini' - 'src/**/conftest.py' - '.github/workflows/test.yml' - +permissions: + contents: read + actions: read jobs: backend_tests: runs-on: ubuntu-latest From 2e15a189917711852acbecd2cfaad9a7c308a2ac Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Fri, 9 Jan 2026 12:01:35 +0530 Subject: [PATCH 03/10] Remove export command --- .github/workflows/deploy.yml | 23 +++++++++++------------ .github/workflows/job-deploy-linux.yml | 8 ++++---- .github/workflows/job-deploy-windows.yml | 1 - .github/workflows/job-deploy.yml | 15 +++++++-------- 4 files changed, 22 insertions(+), 25 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 200c35e4..c3254f14 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -47,14 +47,14 @@ 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: "100" + 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="100" - export AZURE_REGIONS="${{ vars.AZURE_REGIONS }}" - chmod +x infra/scripts/checkquota.sh if ! infra/scripts/checkquota.sh; then # If quota check fails due to insufficient quota, set the flag @@ -171,18 +171,17 @@ jobs: echo "✅ Deployment succeeded." echo "$DEPLOY_OUTPUT" - # Export variables only after successful deploy - export CONTAINER_API_APPURL=$(echo "$DEPLOY_OUTPUT" | jq -r '.containeR_API_APP_FQDN.value') + CONTAINER_API_APPURL=$(echo "$DEPLOY_OUTPUT" | jq -r '.containeR_API_APP_FQDN.value') echo "CONTAINER_API_APPURL=$CONTAINER_API_APPURL" >> $GITHUB_ENV - export CONTAINER_API_APPNAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.containeR_API_APP_NAME.value') + CONTAINER_API_APPNAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.containeR_API_APP_NAME.value') echo "CONTAINER_API_APPNAME=$CONTAINER_API_APPNAME" >> $GITHUB_ENV - export CONTAINER_WEB_APPURL="https://$(echo "$DEPLOY_OUTPUT" | jq -r '.containeR_WEB_APP_FQDN.value')" + CONTAINER_WEB_APPURL="https://$(echo "$DEPLOY_OUTPUT" | jq -r '.containeR_WEB_APP_FQDN.value')" echo "CONTAINER_WEB_APPURL=$CONTAINER_WEB_APPURL" >> $GITHUB_ENV echo "CONTAINER_WEB_APPURL=$CONTAINER_WEB_APPURL" >> $GITHUB_OUTPUT - export CONTAINER_WEB_APPNAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.containeR_WEB_APP_NAME.value') + CONTAINER_WEB_APPNAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.containeR_WEB_APP_NAME.value') echo "CONTAINER_WEB_APPNAME=$CONTAINER_WEB_APPNAME" >> $GITHUB_ENV - name: Register schemas diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml index 791af146..569205d6 100644 --- a/.github/workflows/job-deploy-linux.yml +++ b/.github/workflows/job-deploy-linux.yml @@ -301,17 +301,17 @@ jobs: exit 1 fi - export CONTAINER_API_APPURL="https://$(echo "$DEPLOY_OUTPUT" | jq -r '.CONTAINER_API_APP_FQDN // empty')" + CONTAINER_API_APPURL="https://$(echo "$DEPLOY_OUTPUT" | jq -r '.CONTAINER_API_APP_FQDN // empty')" echo "CONTAINER_API_APPURL=$CONTAINER_API_APPURL" >> $GITHUB_ENV - export CONTAINER_API_APPNAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.CONTAINER_API_APP_NAME // empty') + CONTAINER_API_APPNAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.CONTAINER_API_APP_NAME // empty') echo "CONTAINER_API_APPNAME=$CONTAINER_API_APPNAME" >> $GITHUB_ENV - export CONTAINER_WEB_APPURL="https://$(echo "$DEPLOY_OUTPUT" | jq -r '.CONTAINER_WEB_APP_FQDN // empty')" + CONTAINER_WEB_APPURL="https://$(echo "$DEPLOY_OUTPUT" | jq -r '.CONTAINER_WEB_APP_FQDN // empty')" echo "CONTAINER_WEB_APPURL=$CONTAINER_WEB_APPURL" >> $GITHUB_ENV echo "CONTAINER_WEB_APPURL=$CONTAINER_WEB_APPURL" >> $GITHUB_OUTPUT - export CONTAINER_WEB_APPNAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.CONTAINER_WEB_APP_NAME // empty') + CONTAINER_WEB_APPNAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.CONTAINER_WEB_APP_NAME // empty') echo "CONTAINER_WEB_APPNAME=$CONTAINER_WEB_APPNAME" >> $GITHUB_ENV - name: Register schemas (Linux) diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml index 3573c8f3..abf62d65 100644 --- a/.github/workflows/job-deploy-windows.yml +++ b/.github/workflows/job-deploy-windows.yml @@ -291,7 +291,6 @@ jobs: exit 1 } - # Export variables only after successful deploy $CONTAINER_API_APPURL = "https://$($DEPLOY_OUTPUT.CONTAINER_API_APP_FQDN)" "CONTAINER_API_APPURL=$CONTAINER_API_APPURL" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append diff --git a/.github/workflows/job-deploy.yml b/.github/workflows/job-deploy.yml index 6af0cb1a..f5b29d1c 100644 --- a/.github/workflows/job-deploy.yml +++ b/.github/workflows/job-deploy.yml @@ -317,15 +317,14 @@ jobs: - name: Run Quota Check id: quota-check - shell: bash + 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 }} + 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 AZURE_REGIONS="${{ vars.AZURE_REGIONS }}" - chmod +x infra/scripts/checkquota.sh if ! infra/scripts/checkquota.sh; then if grep -q "No region with sufficient quota found" infra/scripts/checkquota.sh; then From cb82ae07cf20a98996b9027542e32def37e00c51 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Fri, 9 Jan 2026 12:07:36 +0530 Subject: [PATCH 04/10] refactor: remove Curl Azure CLI setup steps and replace with Azure setup actions --- .github/workflows/deploy.yml | 12 +----------- .github/workflows/job-cleanup-deployment.yml | 8 -------- .github/workflows/job-deploy-linux.yml | 16 ++-------------- 3 files changed, 3 insertions(+), 33 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c3254f14..0a6db726 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -35,10 +35,6 @@ jobs: - name: Checkout Code uses: actions/checkout@v5 - - name: Setup Azure CLI - run: | - curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash - az --version # Verify installation - name: Login to Azure run: | @@ -141,10 +137,7 @@ jobs: run: | set -e echo "Fetching deployment output..." - - # Install azd (Azure Developer CLI) - curl -fsSL https://aka.ms/install-azd.sh | bash - + # Generate current timestamp in desired format: YYYY-MM-DDTHH:MM:SS.SSSSSSSZ current_date=$(date -u +"%Y-%m-%dT%H:%M:%S.%7NZ") @@ -304,9 +297,6 @@ jobs: AZURE_LOCATION: ${{ needs.deploy.outputs.AZURE_LOCATION }} ENVIRONMENT_NAME: ${{ needs.deploy.outputs.ENVIRONMENT_NAME }} steps: - - name: Setup Azure CLI - run: curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash - - name: Login to Azure run: | az login --service-principal -u ${{ secrets.AZURE_CLIENT_ID }} -p ${{ secrets.AZURE_CLIENT_SECRET }} --tenant ${{ secrets.AZURE_TENANT_ID }} diff --git a/.github/workflows/job-cleanup-deployment.yml b/.github/workflows/job-cleanup-deployment.yml index f4df28f5..0467b9e0 100644 --- a/.github/workflows/job-cleanup-deployment.yml +++ b/.github/workflows/job-cleanup-deployment.yml @@ -199,14 +199,6 @@ jobs: echo "" echo "✅ All input parameters validated successfully!" - - - 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 diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml index 569205d6..c8a291b3 100644 --- a/.github/workflows/job-deploy-linux.yml +++ b/.github/workflows/job-deploy-linux.yml @@ -197,17 +197,8 @@ jobs: 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 @@ -235,9 +226,6 @@ jobs: echo "Starting azd deployment..." echo "EXP: $EXP" echo "Using Docker Image Tag: $IMAGE_TAG" - - # Install azd (Azure Developer CLI) - curl -fsSL https://aka.ms/install-azd.sh | bash # Generate current timestamp in desired format: YYYY-MM-DDTHH:MM:SS.SSSSSSSZ current_date=$(date -u +"%Y-%m-%dT%H:%M:%S.%7NZ") From 08615de820e20bc84c735638c4519f8c4ba4e70c Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Tue, 13 Jan 2026 14:55:49 +0530 Subject: [PATCH 05/10] ci: fixed Pipeline failures --- .github/workflows/deploy-linux.yml | 188 ++++++++++++++++++-- .github/workflows/deploy-windows.yml | 188 ++++++++++++++++++-- .github/workflows/job-deploy-linux.yml | 4 +- .github/workflows/job-deploy-windows.yml | 4 +- .github/workflows/job-deploy.yml | 47 ++--- .github/workflows/job-send-notification.yml | 6 +- 6 files changed, 390 insertions(+), 47 deletions(-) diff --git a/.github/workflows/deploy-linux.yml b/.github/workflows/deploy-linux.yml index 60c025a2..d65a3c16 100644 --- a/.github/workflows/deploy-linux.yml +++ b/.github/workflows/deploy-linux.yml @@ -96,19 +96,187 @@ 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-windows.yml b/.github/workflows/deploy-windows.yml index 7c4d9d8a..491cb1a7 100644 --- a/.github/workflows/deploy-windows.yml +++ b/.github/workflows/deploy-windows.yml @@ -79,19 +79,187 @@ 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-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml index c8a291b3..214cb6c7 100644 --- a/.github/workflows/job-deploy-linux.yml +++ b/.github/workflows/job-deploy-linux.yml @@ -150,7 +150,7 @@ jobs: # Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (optional, if provided must be valid Resource ID) if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then - if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/microsoft\.operationalinsights/workspaces/[^/]+$ ]]; then + if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/[Rr]esource[Gg]roups/[^/]+/providers/[Mm]icrosoft\.[Oo]perational[Ii]nsights/[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'" @@ -162,7 +162,7 @@ jobs: # Validate AZURE_EXISTING_AI_PROJECT_RESOURCE_ID (optional, if provided must be valid Resource ID) if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then - if [[ ! "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/(Microsoft\.MachineLearningServices/(workspaces|projects)/[^/]+|Microsoft\.CognitiveServices/accounts/[^/]+/projects/[^/]+)$ ]]; then + 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'" diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml index abf62d65..7b0ce43f 100644 --- a/.github/workflows/job-deploy-windows.yml +++ b/.github/workflows/job-deploy-windows.yml @@ -150,7 +150,7 @@ jobs: # Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (optional, if provided must be valid Resource ID) if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then - if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/microsoft\.operationalinsights/workspaces/[^/]+$ ]]; then + if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/[Rr]esource[Gg]roups/[^/]+/providers/[Mm]icrosoft\.[Oo]perational[Ii]nsights/[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'" @@ -162,7 +162,7 @@ jobs: # Validate AZURE_EXISTING_AI_PROJECT_RESOURCE_ID (optional, if provided must be valid Resource ID) if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then - if [[ ! "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/(Microsoft\.MachineLearningServices/(workspaces|projects)/[^/]+|Microsoft\.CognitiveServices/accounts/[^/]+/projects/[^/]+)$ ]]; then + 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'" diff --git a/.github/workflows/job-deploy.yml b/.github/workflows/job-deploy.yml index f5b29d1c..c30c1390 100644 --- a/.github/workflows/job-deploy.yml +++ b/.github/workflows/job-deploy.yml @@ -112,7 +112,7 @@ 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 @@ -226,7 +226,7 @@ jobs: # Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (Azure Resource ID format) if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then - if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/microsoft\.operationalinsights/workspaces/[^/]+$ ]]; then + if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/[Rr]esource[Gg]roups/[^/]+/providers/[Mm]icrosoft\.[Oo]perational[Ii]nsights/[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'" @@ -238,7 +238,7 @@ jobs: # Validate AZURE_EXISTING_AI_PROJECT_RESOURCE_ID (Azure Resource ID format) if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then - if [[ ! "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/resourceGroups/[^/]+/providers/(Microsoft\.MachineLearningServices/(workspaces|projects)/[^/]+|Microsoft\.CognitiveServices/accounts/[^/]+/projects/[^/]+)$ ]]; then + 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'" @@ -284,28 +284,35 @@ jobs: echo "✅ All input parameters validated successfully!" - name: Validate and Auto-Configure EXP + id: configure_exp shell: bash env: INPUT_EXP: ${{ inputs.EXP }} - INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }} - INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }} + 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 [[ "$INPUT_EXP" != "true" ]]; then - if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]] || [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then - echo "🔧 AUTO-ENABLING EXP: EXP parameter values were provided but EXP was not explicitly enabled." - echo "" - echo "You provided values for:" - [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]] && echo " - Azure Log Analytics Workspace ID: '$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID'" - [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]] && echo " - Azure AI Project Resource ID: '$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID'" - echo "" - echo "✅ Automatically enabling EXP to use these values." - echo "EXP=true" >> $GITHUB_ENV - echo "📌 EXP has been automatically enabled for this deployment." - fi + 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 @@ -482,7 +489,7 @@ jobs: INPUT_RESOURCE_GROUP_NAME: ${{ inputs.resource_group_name }} EVENT_NAME: ${{ github.event_name }} WAF_STATUS: ${{ env.WAF_ENABLED == 'true' && '✅ Yes' || '❌ No' }} - EXP_STATUS: ${{ env.EXP == 'true' && '✅ Yes' || '❌ No' }} + EXP_STATUS: ${{ steps.configure_exp.outputs.EXP_ENABLED == 'true' && '✅ Yes' || '❌ No' }} CLEANUP_STATUS: ${{ env.CLEANUP_RESOURCES == 'true' && '✅ Yes' || '❌ No' }} BUILD_DOCKER_STATUS: ${{ env.BUILD_DOCKER_IMAGE == 'true' && '✅ Yes' || '❌ No' }} run: | @@ -529,7 +536,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 }} @@ -547,7 +554,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-send-notification.yml b/.github/workflows/job-send-notification.yml index ff8e3f13..2f43ca53 100644 --- a/.github/workflows/job-send-notification.yml +++ b/.github/workflows/job-send-notification.yml @@ -195,9 +195,9 @@ jobs: fi fi - # Validate QUOTA_FAILED (must be 'true' or 'false') - if [[ "$INPUT_QUOTA_FAILED" != "true" && "$INPUT_QUOTA_FAILED" != "false" ]]; then - echo "❌ ERROR: QUOTA_FAILED must be 'true' or 'false', got: '$INPUT_QUOTA_FAILED'" + # Validate QUOTA_FAILED (must be 'true', '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" From 3b7540cdcc1d589cf17c2041f66d3bbc95bb5ec1 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Tue, 13 Jan 2026 15:01:28 +0530 Subject: [PATCH 06/10] Minor fix --- .github/workflows/job-deploy-linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml index 214cb6c7..1c7ac1fc 100644 --- a/.github/workflows/job-deploy-linux.yml +++ b/.github/workflows/job-deploy-linux.yml @@ -150,7 +150,7 @@ jobs: # Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (optional, if provided must be valid Resource ID) if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then - if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/[Rr]esource[Gg]roups/[^/]+/providers/[Mm]icrosoft\.[Oo]perational[Ii]nsights/[Ww]orkspaces/[^/]+$ ]]; 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'" From 3d41816f44cf35cf76cde1e92473134245ae5b79 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Tue, 13 Jan 2026 15:11:18 +0530 Subject: [PATCH 07/10] Changed Job condition from Always to !Cancelled --- .github/workflows/deploy-orchestrator.yml | 8 ++++---- .github/workflows/job-deploy.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy-orchestrator.yml b/.github/workflows/deploy-orchestrator.yml index 6eca9baa..f8a4d7e6 100644 --- a/.github/workflows/deploy-orchestrator.yml +++ b/.github/workflows/deploy-orchestrator.yml @@ -76,7 +76,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: @@ -96,7 +96,7 @@ jobs: secrets: inherit e2e-test: - if: always() && ((needs.deploy.result == 'success' && needs.deploy.outputs.CONTAINER_WEB_APPURL != '') || (inputs.existing_webapp_url != '' && inputs.existing_webapp_url != null)) && (inputs.trigger_type != 'workflow_dispatch' || (inputs.run_e2e_tests != 'None' && inputs.run_e2e_tests != '' && inputs.run_e2e_tests != null)) + if: "!cancelled() && ((needs.deploy.result == 'success' && needs.deploy.outputs.CONTAINER_WEB_APPURL != '') || (inputs.existing_webapp_url != '' && inputs.existing_webapp_url != null)) && (inputs.trigger_type != 'workflow_dispatch' || (inputs.run_e2e_tests != 'None' && inputs.run_e2e_tests != '' && inputs.run_e2e_tests != null))" needs: [docker-build, deploy] uses: ./.github/workflows/test-automation-v2.yml with: @@ -105,7 +105,7 @@ jobs: secrets: inherit send-notification: - if: always() + if: "!cancelled()" needs: [docker-build, deploy, e2e-test] uses: ./.github/workflows/job-send-notification.yml with: @@ -124,7 +124,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.result == 'success' && needs.deploy.outputs.RESOURCE_GROUP_NAME != '' && inputs.existing_webapp_url == '' && (inputs.trigger_type != 'workflow_dispatch' || inputs.cleanup_resources)" needs: [docker-build, deploy, e2e-test] uses: ./.github/workflows/job-cleanup-deployment.yml with: diff --git a/.github/workflows/job-deploy.yml b/.github/workflows/job-deploy.yml index c30c1390..40ed81e9 100644 --- a/.github/workflows/job-deploy.yml +++ b/.github/workflows/job-deploy.yml @@ -527,7 +527,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 }} @@ -545,7 +545,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 }} From 59b207e13372e50a3385a0af487e1bf3f94bc46d Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Fri, 16 Jan 2026 14:47:22 +0530 Subject: [PATCH 08/10] Pass Exp to Notification Job --- .github/workflows/deploy-orchestrator.yml | 2 +- .github/workflows/job-deploy.yml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-orchestrator.yml b/.github/workflows/deploy-orchestrator.yml index f8a4d7e6..20858bd2 100644 --- a/.github/workflows/deploy-orchestrator.yml +++ b/.github/workflows/deploy-orchestrator.yml @@ -111,7 +111,7 @@ jobs: with: trigger_type: ${{ inputs.trigger_type }} waf_enabled: ${{ inputs.waf_enabled }} - EXP: ${{ inputs.EXP }} + EXP: ${{ needs.deploy.outputs.EXP_ENABLED }} run_e2e_tests: ${{ inputs.run_e2e_tests }} existing_webapp_url: ${{ inputs.existing_webapp_url }} deploy_result: ${{ needs.deploy.result }} diff --git a/.github/workflows/job-deploy.yml b/.github/workflows/job-deploy.yml index 40ed81e9..5e3440c5 100644 --- a/.github/workflows/job-deploy.yml +++ b/.github/workflows/job-deploy.yml @@ -88,6 +88,9 @@ on: QUOTA_FAILED: description: "Quota Check Failed Flag" value: ${{ jobs.azure-setup.outputs.QUOTA_FAILED }} + EXP_ENABLED: + description: "EXP Flag" + value: ${{ jobs.azure-setup.outputs.EXP_ENABLED }} env: GPT_MIN_CAPACITY: 100 From b9c65d22d6631cc36850d24cc4d96bdfa9e626c2 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Fri, 16 Jan 2026 17:09:14 +0530 Subject: [PATCH 09/10] refactor: update EXP input handling in deployment workflows --- .github/workflows/deploy-orchestrator.yml | 2 +- .github/workflows/job-deploy.yml | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/deploy-orchestrator.yml b/.github/workflows/deploy-orchestrator.yml index 20858bd2..f8a4d7e6 100644 --- a/.github/workflows/deploy-orchestrator.yml +++ b/.github/workflows/deploy-orchestrator.yml @@ -111,7 +111,7 @@ jobs: with: trigger_type: ${{ inputs.trigger_type }} waf_enabled: ${{ inputs.waf_enabled }} - EXP: ${{ needs.deploy.outputs.EXP_ENABLED }} + EXP: ${{ inputs.EXP }} run_e2e_tests: ${{ inputs.run_e2e_tests }} existing_webapp_url: ${{ inputs.existing_webapp_url }} deploy_result: ${{ needs.deploy.result }} diff --git a/.github/workflows/job-deploy.yml b/.github/workflows/job-deploy.yml index 5e3440c5..40ed81e9 100644 --- a/.github/workflows/job-deploy.yml +++ b/.github/workflows/job-deploy.yml @@ -88,9 +88,6 @@ on: QUOTA_FAILED: description: "Quota Check Failed Flag" value: ${{ jobs.azure-setup.outputs.QUOTA_FAILED }} - EXP_ENABLED: - description: "EXP Flag" - value: ${{ jobs.azure-setup.outputs.EXP_ENABLED }} env: GPT_MIN_CAPACITY: 100 From 90d492ea01ac8e3477427ecdf4162b7ea229358e Mon Sep 17 00:00:00 2001 From: "Prekshith D J (Persistent Systems Inc)" Date: Wed, 28 Jan 2026 12:03:30 +0530 Subject: [PATCH 10/10] Fix the issue with devcontainer --- .devcontainer/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index fda78e10..f88097e4 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -7,6 +7,9 @@ FROM ghcr.io/astral-sh/uv:$UV_VERSION AS uv # Use Debian-based VS Code Dev Container as base FROM mcr.microsoft.com/vscode/devcontainers/base:$DEBIAN_VERSION +# Remove Yarn repository to avoid GPG key expiration issue +RUN rm -f /etc/apt/sources.list.d/yarn.list + # Install dependencies and Node.js 20+ from NodeSource RUN apt-get update \ && apt-get install -y --no-install-recommends \