From 97cf33c404834daecbcd708c550013bd73e0b1e9 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Fri, 9 Jan 2026 14:04:24 +0530 Subject: [PATCH 1/3] Add validation step for Inputs and Map Inputs to env --- .github/workflows/job-deploy-linux.yml | 225 +++++++++++++++++--- .github/workflows/job-deploy-windows.yml | 212 ++++++++++++++++-- .github/workflows/job-deploy.yml | 208 ++++++++++++++++-- .github/workflows/job-send-notification.yml | 224 +++++++++++++++++-- 4 files changed, 792 insertions(+), 77 deletions(-) diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml index 20a8591d..cad42669 100644 --- a/.github/workflows/job-deploy-linux.yml +++ b/.github/workflows/job-deploy-linux.yml @@ -47,13 +47,147 @@ jobs: outputs: WEB_APPURL: ${{ steps.get_output_linux.outputs.WEB_APPURL }} steps: + - name: Validate Workflow Input Parameters + shell: bash + env: + INPUT_ENV_NAME: ${{ inputs.ENV_NAME }} + INPUT_AZURE_ENV_OPENAI_LOCATION: ${{ inputs.AZURE_ENV_OPENAI_LOCATION }} + INPUT_AZURE_LOCATION: ${{ inputs.AZURE_LOCATION }} + INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }} + INPUT_IMAGE_TAG: ${{ inputs.IMAGE_TAG }} + INPUT_BUILD_DOCKER_IMAGE: ${{ inputs.BUILD_DOCKER_IMAGE }} + INPUT_EXP: ${{ inputs.EXP }} + INPUT_WAF_ENABLED: ${{ inputs.WAF_ENABLED }} + INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }} + INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }} + run: | + echo "🔍 Validating workflow input parameters..." + VALIDATION_FAILED=false + + # Validate ENV_NAME (required, alphanumeric and hyphens) + if [[ -z "$INPUT_ENV_NAME" ]]; then + echo "❌ ERROR: ENV_NAME is required but not provided" + VALIDATION_FAILED=true + elif [[ ! "$INPUT_ENV_NAME" =~ ^[a-zA-Z0-9-]+$ ]]; then + echo "❌ ERROR: ENV_NAME '$INPUT_ENV_NAME' is invalid. Must contain only alphanumerics and hyphens" + VALIDATION_FAILED=true + else + echo "✅ ENV_NAME: '$INPUT_ENV_NAME' is valid" + fi + + # Validate AZURE_ENV_OPENAI_LOCATION (required, Azure region format) + if [[ -z "$INPUT_AZURE_ENV_OPENAI_LOCATION" ]]; then + echo "❌ ERROR: AZURE_ENV_OPENAI_LOCATION is required but not provided" + VALIDATION_FAILED=true + elif [[ ! "$INPUT_AZURE_ENV_OPENAI_LOCATION" =~ ^[a-z0-9]+$ ]]; then + echo "❌ ERROR: AZURE_ENV_OPENAI_LOCATION '$INPUT_AZURE_ENV_OPENAI_LOCATION' is invalid. Must contain only lowercase letters and numbers" + VALIDATION_FAILED=true + else + echo "✅ AZURE_ENV_OPENAI_LOCATION: '$INPUT_AZURE_ENV_OPENAI_LOCATION' is valid" + fi + + # Validate AZURE_LOCATION (required, Azure region format) + if [[ -z "$INPUT_AZURE_LOCATION" ]]; then + echo "❌ ERROR: AZURE_LOCATION is required but not provided" + VALIDATION_FAILED=true + elif [[ ! "$INPUT_AZURE_LOCATION" =~ ^[a-z0-9]+$ ]]; then + echo "❌ ERROR: AZURE_LOCATION '$INPUT_AZURE_LOCATION' is invalid. Must contain only lowercase letters and numbers" + VALIDATION_FAILED=true + else + echo "✅ AZURE_LOCATION: '$INPUT_AZURE_LOCATION' is valid" + fi + + # Validate RESOURCE_GROUP_NAME (required, Azure naming convention) + if [[ -z "$INPUT_RESOURCE_GROUP_NAME" ]]; then + echo "❌ ERROR: RESOURCE_GROUP_NAME is required but not provided" + VALIDATION_FAILED=true + elif [[ ! "$INPUT_RESOURCE_GROUP_NAME" =~ ^[a-zA-Z0-9._\(\)-]+$ ]] || [[ "$INPUT_RESOURCE_GROUP_NAME" =~ \.$ ]]; then + echo "❌ ERROR: RESOURCE_GROUP_NAME '$INPUT_RESOURCE_GROUP_NAME' is invalid. Must contain only alphanumerics, periods, underscores, hyphens, and parentheses. Cannot end with period." + VALIDATION_FAILED=true + elif [[ ${#INPUT_RESOURCE_GROUP_NAME} -gt 90 ]]; then + echo "❌ ERROR: RESOURCE_GROUP_NAME '$INPUT_RESOURCE_GROUP_NAME' exceeds 90 characters" + VALIDATION_FAILED=true + else + echo "✅ RESOURCE_GROUP_NAME: '$INPUT_RESOURCE_GROUP_NAME' is valid" + fi + + # Validate IMAGE_TAG (required, Docker tag pattern) + if [[ -z "$INPUT_IMAGE_TAG" ]]; then + echo "❌ ERROR: IMAGE_TAG is required but not provided" + VALIDATION_FAILED=true + elif [[ ! "$INPUT_IMAGE_TAG" =~ ^[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}$ ]]; then + echo "❌ ERROR: IMAGE_TAG '$INPUT_IMAGE_TAG' is invalid. Must start with alphanumeric or underscore, max 128 characters" + VALIDATION_FAILED=true + else + echo "✅ IMAGE_TAG: '$INPUT_IMAGE_TAG' is valid" + fi + + # Validate BUILD_DOCKER_IMAGE (required, must be 'true' or 'false') + if [[ "$INPUT_BUILD_DOCKER_IMAGE" != "true" && "$INPUT_BUILD_DOCKER_IMAGE" != "false" ]]; then + echo "❌ ERROR: BUILD_DOCKER_IMAGE must be 'true' or 'false', got: '$INPUT_BUILD_DOCKER_IMAGE'" + VALIDATION_FAILED=true + else + echo "✅ BUILD_DOCKER_IMAGE: '$INPUT_BUILD_DOCKER_IMAGE' is valid" + fi + + # Validate EXP (required, must be 'true' or 'false') + if [[ "$INPUT_EXP" != "true" && "$INPUT_EXP" != "false" ]]; then + echo "❌ ERROR: EXP must be 'true' or 'false', got: '$INPUT_EXP'" + VALIDATION_FAILED=true + else + echo "✅ EXP: '$INPUT_EXP' is valid" + fi + + # Validate WAF_ENABLED (must be 'true' or 'false') + if [[ "$INPUT_WAF_ENABLED" != "true" && "$INPUT_WAF_ENABLED" != "false" ]]; then + echo "❌ ERROR: WAF_ENABLED must be 'true' or 'false', got: '$INPUT_WAF_ENABLED'" + VALIDATION_FAILED=true + else + echo "✅ WAF_ENABLED: '$INPUT_WAF_ENABLED' is valid" + fi + + # Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (optional, if provided must be valid Resource ID) + if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then + if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/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,45 +217,55 @@ jobs: - name: Deploy using azd up and extract values (Linux) id: get_output_linux shell: bash + env: + ENV_NAME: ${{ inputs.ENV_NAME }} + AZURE_ENV_OPENAI_LOCATION: ${{ inputs.AZURE_ENV_OPENAI_LOCATION }} + AZURE_LOCATION: ${{ inputs.AZURE_LOCATION }} + RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }} + IMAGE_TAG: ${{ inputs.IMAGE_TAG }} + BUILD_DOCKER_IMAGE: ${{ inputs.BUILD_DOCKER_IMAGE }} + EXP: ${{ inputs.EXP }} + INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }} + INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }} run: | set -e echo "Creating environment..." - azd env new ${{ inputs.ENV_NAME }} --no-prompt - echo "Environment created: ${{ inputs.ENV_NAME }}" + azd env new "$ENV_NAME" --no-prompt + echo "Environment created: $ENV_NAME" echo "Setting default subscription..." - azd config set defaults.subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }} + azd config set defaults.subscription "${{ secrets.AZURE_SUBSCRIPTION_ID }}" # Set additional parameters azd env set AZURE_SUBSCRIPTION_ID="${{ secrets.AZURE_SUBSCRIPTION_ID }}" - azd env set AZURE_ENV_OPENAI_LOCATION="${{ inputs.AZURE_ENV_OPENAI_LOCATION }}" - azd env set AZURE_LOCATION="${{ inputs.AZURE_LOCATION }}" - azd env set AZURE_RESOURCE_GROUP="${{ inputs.RESOURCE_GROUP_NAME }}" - azd env set AZURE_ENV_IMAGETAG="${{ inputs.IMAGE_TAG }}" + azd env set AZURE_ENV_OPENAI_LOCATION="$AZURE_ENV_OPENAI_LOCATION" + azd env set AZURE_LOCATION="$AZURE_LOCATION" + azd env set AZURE_RESOURCE_GROUP="$RESOURCE_GROUP_NAME" + azd env set AZURE_ENV_IMAGETAG="$IMAGE_TAG" # Set ACR name only when building Docker image - if [[ "${{ inputs.BUILD_DOCKER_IMAGE }}" == "true" ]]; then + if [[ "$BUILD_DOCKER_IMAGE" == "true" ]]; then # Extract ACR name from login server and set as environment variable - ACR_NAME=$(echo "${{ secrets.ACR_TEST_USERNAME }}") + ACR_NAME="${{ secrets.ACR_TEST_USERNAME }}" azd env set AZURE_ENV_ACR_NAME="$ACR_NAME" echo "Set ACR name to: $ACR_NAME" else echo "Skipping ACR name configuration (using existing image)" fi - if [[ "${{ inputs.EXP }}" == "true" ]]; then + if [[ "$EXP" == "true" ]]; then echo "✅ EXP ENABLED - Setting EXP parameters..." # Set EXP variables dynamically - if [[ -n "${{ github.event.inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}" ]]; then - EXP_LOG_ANALYTICS_ID="${{ github.event.inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}" + if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then + EXP_LOG_ANALYTICS_ID="$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" else EXP_LOG_ANALYTICS_ID="${{ secrets.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}" fi - if [[ -n "${{ github.event.inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" ]]; then - EXP_AI_PROJECT_ID="${{ github.event.inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" + if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then + EXP_AI_PROJECT_ID="$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" else EXP_AI_PROJECT_ID="${{ secrets.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" fi @@ -132,7 +276,7 @@ jobs: azd env set AZURE_EXISTING_AI_PROJECT_RESOURCE_ID="$EXP_AI_PROJECT_ID" else echo "❌ EXP DISABLED - Skipping EXP parameters" - if [[ -n "${{ github.event.inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}" ]] || [[ -n "${{ github.event.inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" ]]; then + if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]] || [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then echo "⚠️ Warning: EXP parameter values provided but EXP is disabled. These values will be ignored." fi fi @@ -181,9 +325,10 @@ jobs: id: post_deploy env: AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} run: | set -e - az account set --subscription "${{ secrets.AZURE_SUBSCRIPTION_ID }}" + az account set --subscription "$AZURE_SUBSCRIPTION_ID" echo "Running post-deployment script..." @@ -194,26 +339,54 @@ jobs: "$AZURE_COSMOSDB_ACCOUNT" \ "$RESOURCE_GROUP_NAME" \ "$AI_SEARCH_SERVICE_NAME" \ - "${{ secrets.AZURE_CLIENT_ID }}" \ + "$AZURE_CLIENT_ID" \ "$AI_FOUNDRY_RESOURCE_ID" - name: Generate Deploy Job Summary if: always() + env: + RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }} + WAF_ENABLED: ${{ inputs.WAF_ENABLED }} + EXP: ${{ inputs.EXP }} + AZURE_LOCATION: ${{ inputs.AZURE_LOCATION }} + AZURE_ENV_OPENAI_LOCATION: ${{ inputs.AZURE_ENV_OPENAI_LOCATION }} + IMAGE_TAG: ${{ inputs.IMAGE_TAG }} + JOB_STATUS: ${{ job.status }} + WEB_APPURL: ${{ steps.get_output_linux.outputs.WEB_APPURL }} run: | echo "## 🚀 Deploy Job Summary (Linux)" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY - echo "| **Job Status** | ${{ job.status == 'success' && '✅ Success' || '❌ Failed' }} |" >> $GITHUB_STEP_SUMMARY - echo "| **Resource Group** | \`${{ inputs.RESOURCE_GROUP_NAME }}\` |" >> $GITHUB_STEP_SUMMARY - echo "| **Configuration Type** | \`${{ inputs.WAF_ENABLED == 'true' && inputs.EXP == 'true' && 'WAF + EXP' || inputs.WAF_ENABLED == 'true' && inputs.EXP != 'true' && 'WAF + Non-EXP' || inputs.WAF_ENABLED != 'true' && inputs.EXP == 'true' && 'Non-WAF + EXP' || 'Non-WAF + Non-EXP' }}\` |" >> $GITHUB_STEP_SUMMARY - echo "| **Azure Region (Infrastructure)** | \`${{ inputs.AZURE_LOCATION }}\` |" >> $GITHUB_STEP_SUMMARY - echo "| **Azure OpenAI Region** | \`${{ inputs.AZURE_ENV_OPENAI_LOCATION }}\` |" >> $GITHUB_STEP_SUMMARY - echo "| **Docker Image Tag** | \`${{ inputs.IMAGE_TAG }}\` |" >> $GITHUB_STEP_SUMMARY + + if [[ "$JOB_STATUS" == "success" ]]; then + echo "| **Job Status** | ✅ Success |" >> $GITHUB_STEP_SUMMARY + else + echo "| **Job Status** | ❌ Failed |" >> $GITHUB_STEP_SUMMARY + fi + + echo "| **Resource Group** | \`$RESOURCE_GROUP_NAME\` |" >> $GITHUB_STEP_SUMMARY + + # Determine configuration type + if [[ "$WAF_ENABLED" == "true" && "$EXP" == "true" ]]; then + CONFIG_TYPE="WAF + EXP" + elif [[ "$WAF_ENABLED" == "true" && "$EXP" != "true" ]]; then + CONFIG_TYPE="WAF + Non-EXP" + elif [[ "$WAF_ENABLED" != "true" && "$EXP" == "true" ]]; then + CONFIG_TYPE="Non-WAF + EXP" + else + CONFIG_TYPE="Non-WAF + Non-EXP" + fi + echo "| **Configuration Type** | \`$CONFIG_TYPE\` |" >> $GITHUB_STEP_SUMMARY + + echo "| **Azure Region (Infrastructure)** | \`$AZURE_LOCATION\` |" >> $GITHUB_STEP_SUMMARY + echo "| **Azure OpenAI Region** | \`$AZURE_ENV_OPENAI_LOCATION\` |" >> $GITHUB_STEP_SUMMARY + echo "| **Docker Image Tag** | \`$IMAGE_TAG\` |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - if [[ "${{ job.status }}" == "success" ]]; then + + if [[ "$JOB_STATUS" == "success" ]]; then echo "### ✅ Deployment Details" >> $GITHUB_STEP_SUMMARY - echo "- **Web App URL**: [${{ steps.get_output_linux.outputs.WEB_APPURL }}](${{ steps.get_output_linux.outputs.WEB_APPURL }})" >> $GITHUB_STEP_SUMMARY + echo "- **Web App URL**: [$WEB_APPURL]($WEB_APPURL)" >> $GITHUB_STEP_SUMMARY echo "- Successfully deployed to Azure with all resources configured" >> $GITHUB_STEP_SUMMARY echo "- Post-deployment scripts executed successfully" >> $GITHUB_STEP_SUMMARY else diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml index eb4cd0b6..1855c858 100644 --- a/.github/workflows/job-deploy-windows.yml +++ b/.github/workflows/job-deploy-windows.yml @@ -47,13 +47,148 @@ jobs: outputs: WEB_APPURL: ${{ steps.get_output_windows.outputs.WEB_APPURL }} steps: + - name: Validate Workflow Input Parameters + shell: bash + env: + INPUT_ENV_NAME: ${{ inputs.ENV_NAME }} + INPUT_AZURE_ENV_OPENAI_LOCATION: ${{ inputs.AZURE_ENV_OPENAI_LOCATION }} + INPUT_AZURE_LOCATION: ${{ inputs.AZURE_LOCATION }} + INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }} + INPUT_IMAGE_TAG: ${{ inputs.IMAGE_TAG }} + INPUT_BUILD_DOCKER_IMAGE: ${{ inputs.BUILD_DOCKER_IMAGE }} + INPUT_EXP: ${{ inputs.EXP }} + INPUT_WAF_ENABLED: ${{ inputs.WAF_ENABLED }} + INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }} + INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }} + run: | + echo "🔍 Validating workflow input parameters..." + VALIDATION_FAILED=false + + # Validate ENV_NAME (required, alphanumeric and hyphens) + if [[ -z "$INPUT_ENV_NAME" ]]; then + echo "❌ ERROR: ENV_NAME is required but not provided" + VALIDATION_FAILED=true + elif [[ ! "$INPUT_ENV_NAME" =~ ^[a-zA-Z0-9-]+$ ]]; then + echo "❌ ERROR: ENV_NAME '$INPUT_ENV_NAME' is invalid. Must contain only alphanumerics and hyphens" + VALIDATION_FAILED=true + else + echo "✅ ENV_NAME: '$INPUT_ENV_NAME' is valid" + fi + + # Validate AZURE_ENV_OPENAI_LOCATION (required, Azure region format) + if [[ -z "$INPUT_AZURE_ENV_OPENAI_LOCATION" ]]; then + echo "❌ ERROR: AZURE_ENV_OPENAI_LOCATION is required but not provided" + VALIDATION_FAILED=true + elif [[ ! "$INPUT_AZURE_ENV_OPENAI_LOCATION" =~ ^[a-z0-9]+$ ]]; then + echo "❌ ERROR: AZURE_ENV_OPENAI_LOCATION '$INPUT_AZURE_ENV_OPENAI_LOCATION' is invalid. Must contain only lowercase letters and numbers" + VALIDATION_FAILED=true + else + echo "✅ AZURE_ENV_OPENAI_LOCATION: '$INPUT_AZURE_ENV_OPENAI_LOCATION' is valid" + fi + + # Validate AZURE_LOCATION (required, Azure region format) + if [[ -z "$INPUT_AZURE_LOCATION" ]]; then + echo "❌ ERROR: AZURE_LOCATION is required but not provided" + VALIDATION_FAILED=true + elif [[ ! "$INPUT_AZURE_LOCATION" =~ ^[a-z0-9]+$ ]]; then + echo "❌ ERROR: AZURE_LOCATION '$INPUT_AZURE_LOCATION' is invalid. Must contain only lowercase letters and numbers" + VALIDATION_FAILED=true + else + echo "✅ AZURE_LOCATION: '$INPUT_AZURE_LOCATION' is valid" + fi + + # Validate RESOURCE_GROUP_NAME (required, Azure naming convention) + if [[ -z "$INPUT_RESOURCE_GROUP_NAME" ]]; then + echo "❌ ERROR: RESOURCE_GROUP_NAME is required but not provided" + VALIDATION_FAILED=true + elif [[ ! "$INPUT_RESOURCE_GROUP_NAME" =~ ^[a-zA-Z0-9._\(\)-]+$ ]] || [[ "$INPUT_RESOURCE_GROUP_NAME" =~ \.$ ]]; then + echo "❌ ERROR: RESOURCE_GROUP_NAME '$INPUT_RESOURCE_GROUP_NAME' is invalid. Must contain only alphanumerics, periods, underscores, hyphens, and parentheses. Cannot end with period." + VALIDATION_FAILED=true + elif [[ ${#INPUT_RESOURCE_GROUP_NAME} -gt 90 ]]; then + echo "❌ ERROR: RESOURCE_GROUP_NAME '$INPUT_RESOURCE_GROUP_NAME' exceeds 90 characters" + VALIDATION_FAILED=true + else + echo "✅ RESOURCE_GROUP_NAME: '$INPUT_RESOURCE_GROUP_NAME' is valid" + fi + + # Validate IMAGE_TAG (required, Docker tag pattern) + if [[ -z "$INPUT_IMAGE_TAG" ]]; then + echo "❌ ERROR: IMAGE_TAG is required but not provided" + VALIDATION_FAILED=true + elif [[ ! "$INPUT_IMAGE_TAG" =~ ^[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}$ ]]; then + echo "❌ ERROR: IMAGE_TAG '$INPUT_IMAGE_TAG' is invalid. Must start with alphanumeric or underscore, max 128 characters" + VALIDATION_FAILED=true + else + echo "✅ IMAGE_TAG: '$INPUT_IMAGE_TAG' is valid" + fi + + # Validate BUILD_DOCKER_IMAGE (required, must be 'true' or 'false') + if [[ "$INPUT_BUILD_DOCKER_IMAGE" != "true" && "$INPUT_BUILD_DOCKER_IMAGE" != "false" ]]; then + echo "❌ ERROR: BUILD_DOCKER_IMAGE must be 'true' or 'false', got: '$INPUT_BUILD_DOCKER_IMAGE'" + VALIDATION_FAILED=true + else + echo "✅ BUILD_DOCKER_IMAGE: '$INPUT_BUILD_DOCKER_IMAGE' is valid" + fi + + # Validate EXP (required, must be 'true' or 'false') + if [[ "$INPUT_EXP" != "true" && "$INPUT_EXP" != "false" ]]; then + echo "❌ ERROR: EXP must be 'true' or 'false', got: '$INPUT_EXP'" + VALIDATION_FAILED=true + else + echo "✅ EXP: '$INPUT_EXP' is valid" + fi + + # Validate WAF_ENABLED (must be 'true' or 'false') + if [[ "$INPUT_WAF_ENABLED" != "true" && "$INPUT_WAF_ENABLED" != "false" ]]; then + echo "❌ ERROR: WAF_ENABLED must be 'true' or 'false', got: '$INPUT_WAF_ENABLED'" + VALIDATION_FAILED=true + else + echo "✅ WAF_ENABLED: '$INPUT_WAF_ENABLED' is valid" + fi + + # Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (optional, if provided must be valid Resource ID) + if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then + if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/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,25 +210,35 @@ jobs: - name: Deploy using azd up and extract values (Windows) id: get_output_windows shell: pwsh + env: + ENV_NAME: ${{ inputs.ENV_NAME }} + AZURE_ENV_OPENAI_LOCATION: ${{ inputs.AZURE_ENV_OPENAI_LOCATION }} + AZURE_LOCATION: ${{ inputs.AZURE_LOCATION }} + RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }} + IMAGE_TAG: ${{ inputs.IMAGE_TAG }} + BUILD_DOCKER_IMAGE: ${{ inputs.BUILD_DOCKER_IMAGE }} + EXP: ${{ inputs.EXP }} + INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }} + INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }} run: | $ErrorActionPreference = "Stop" Write-Host "Creating environment..." - azd env new ${{ inputs.ENV_NAME }} --no-prompt - Write-Host "Environment created: ${{ inputs.ENV_NAME }}" + azd env new $env:ENV_NAME --no-prompt + Write-Host "Environment created: $env:ENV_NAME" Write-Host "Setting default subscription..." azd config set defaults.subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }} # Set additional parameters azd env set AZURE_SUBSCRIPTION_ID="${{ secrets.AZURE_SUBSCRIPTION_ID }}" - azd env set AZURE_ENV_OPENAI_LOCATION="${{ inputs.AZURE_ENV_OPENAI_LOCATION }}" - azd env set AZURE_LOCATION="${{ inputs.AZURE_LOCATION }}" - azd env set AZURE_RESOURCE_GROUP="${{ inputs.RESOURCE_GROUP_NAME }}" - azd env set AZURE_ENV_IMAGETAG="${{ inputs.IMAGE_TAG }}" + azd env set AZURE_ENV_OPENAI_LOCATION="$env:AZURE_ENV_OPENAI_LOCATION" + azd env set AZURE_LOCATION="$env:AZURE_LOCATION" + azd env set AZURE_RESOURCE_GROUP="$env:RESOURCE_GROUP_NAME" + azd env set AZURE_ENV_IMAGETAG="$env:IMAGE_TAG" # Set ACR name only when building Docker image - if ("${{ inputs.BUILD_DOCKER_IMAGE }}" -eq "true") { + if ($env:BUILD_DOCKER_IMAGE -eq "true") { $ACR_NAME = "${{ secrets.ACR_TEST_USERNAME }}" azd env set AZURE_ENV_ACR_NAME="$ACR_NAME" Write-Host "Set ACR name to: $ACR_NAME" @@ -101,18 +246,18 @@ jobs: Write-Host "Skipping ACR name configuration (using existing image)" } - if ("${{ inputs.EXP }}" -eq "true") { + if ($env:EXP -eq "true") { Write-Host "✅ EXP ENABLED - Setting EXP parameters..." # Set EXP variables dynamically - if ("${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}" -ne "") { - $EXP_LOG_ANALYTICS_ID = "${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}" + if ($env:INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID -ne "") { + $EXP_LOG_ANALYTICS_ID = $env:INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID } else { $EXP_LOG_ANALYTICS_ID = "${{ secrets.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }}" } - if ("${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" -ne "") { - $EXP_AI_PROJECT_ID = "${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" + if ($env:INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID -ne "") { + $EXP_AI_PROJECT_ID = $env:INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID } else { $EXP_AI_PROJECT_ID = "${{ secrets.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }}" } @@ -190,21 +335,46 @@ jobs: - name: Generate Deploy Job Summary if: always() shell: bash + env: + RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }} + WAF_ENABLED: ${{ inputs.WAF_ENABLED }} + EXP: ${{ inputs.EXP }} + AZURE_LOCATION: ${{ inputs.AZURE_LOCATION }} + AZURE_ENV_OPENAI_LOCATION: ${{ inputs.AZURE_ENV_OPENAI_LOCATION }} + IMAGE_TAG: ${{ inputs.IMAGE_TAG }} + JOB_STATUS: ${{ job.status }} + WEB_APPURL: ${{ steps.get_output_windows.outputs.WEB_APPURL }} run: | echo "## 🚀 Deploy Job Summary (Windows)" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY - echo "| **Job Status** | ${{ job.status == 'success' && '✅ Success' || '❌ Failed' }} |" >> $GITHUB_STEP_SUMMARY - echo "| **Resource Group** | \`${{ inputs.RESOURCE_GROUP_NAME }}\` |" >> $GITHUB_STEP_SUMMARY - echo "| **Configuration Type** | \`${{ inputs.WAF_ENABLED == 'true' && inputs.EXP == 'true' && 'WAF + EXP' || inputs.WAF_ENABLED == 'true' && inputs.EXP != 'true' && 'WAF + Non-EXP' || inputs.WAF_ENABLED != 'true' && inputs.EXP == 'true' && 'Non-WAF + EXP' || 'Non-WAF + Non-EXP' }}\` |" >> $GITHUB_STEP_SUMMARY - echo "| **Azure Region (Infrastructure)** | \`${{ inputs.AZURE_LOCATION }}\` |" >> $GITHUB_STEP_SUMMARY - echo "| **Azure OpenAI Region** | \`${{ inputs.AZURE_ENV_OPENAI_LOCATION }}\` |" >> $GITHUB_STEP_SUMMARY - echo "| **Docker Image Tag** | \`${{ inputs.IMAGE_TAG }}\` |" >> $GITHUB_STEP_SUMMARY + if [[ "$JOB_STATUS" == "success" ]]; then + echo "| **Job Status** | ✅ Success |" >> $GITHUB_STEP_SUMMARY + else + echo "| **Job Status** | ❌ Failed |" >> $GITHUB_STEP_SUMMARY + fi + echo "| **Resource Group** | \`$RESOURCE_GROUP_NAME\` |" >> $GITHUB_STEP_SUMMARY + + # Determine configuration type + if [[ "$WAF_ENABLED" == "true" && "$EXP" == "true" ]]; then + CONFIG_TYPE="WAF + EXP" + elif [[ "$WAF_ENABLED" == "true" && "$EXP" != "true" ]]; then + CONFIG_TYPE="WAF + Non-EXP" + elif [[ "$WAF_ENABLED" != "true" && "$EXP" == "true" ]]; then + CONFIG_TYPE="Non-WAF + EXP" + else + CONFIG_TYPE="Non-WAF + Non-EXP" + fi + echo "| **Configuration Type** | \`$CONFIG_TYPE\` |" >> $GITHUB_STEP_SUMMARY + + echo "| **Azure Region (Infrastructure)** | \`$AZURE_LOCATION\` |" >> $GITHUB_STEP_SUMMARY + echo "| **Azure OpenAI Region** | \`$AZURE_ENV_OPENAI_LOCATION\` |" >> $GITHUB_STEP_SUMMARY + echo "| **Docker Image Tag** | \`$IMAGE_TAG\` |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - if [[ "${{ job.status }}" == "success" ]]; then + if [[ "$JOB_STATUS" == "success" ]]; then echo "### ✅ Deployment Details" >> $GITHUB_STEP_SUMMARY - echo "- **Web App URL**: [${{ steps.get_output_windows.outputs.WEB_APPURL }}](${{ steps.get_output_windows.outputs.WEB_APPURL }})" >> $GITHUB_STEP_SUMMARY + echo "- **Web App URL**: [$WEB_APPURL]($WEB_APPURL)" >> $GITHUB_STEP_SUMMARY echo "- Successfully deployed to Azure with all resources configured" >> $GITHUB_STEP_SUMMARY echo "- Post-deployment scripts executed successfully" >> $GITHUB_STEP_SUMMARY else diff --git a/.github/workflows/job-deploy.yml b/.github/workflows/job-deploy.yml index 35d737cf..1a6f44ff 100644 --- a/.github/workflows/job-deploy.yml +++ b/.github/workflows/job-deploy.yml @@ -113,18 +113,189 @@ 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_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 [[ -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 @@ -176,13 +347,15 @@ jobs: - name: Set Deployment Region id: set_region shell: bash + env: + INPUT_AZURE_LOCATION: ${{ inputs.azure_location }} run: | echo "Selected Region from Quota Check: $VALID_REGION" echo "AZURE_ENV_OPENAI_LOCATION=$VALID_REGION" >> $GITHUB_ENV echo "AZURE_ENV_OPENAI_LOCATION=$VALID_REGION" >> $GITHUB_OUTPUT - if [[ "${{ inputs.trigger_type }}" == "workflow_dispatch" && -n "${{ inputs.azure_location }}" ]]; then - USER_SELECTED_LOCATION="${{ inputs.azure_location }}" + if [[ "${{ inputs.trigger_type }}" == "workflow_dispatch" && -n "$INPUT_AZURE_LOCATION" ]]; then + USER_SELECTED_LOCATION="$INPUT_AZURE_LOCATION" echo "Using user-selected Azure location: $USER_SELECTED_LOCATION" echo "AZURE_LOCATION=$USER_SELECTED_LOCATION" >> $GITHUB_ENV echo "AZURE_LOCATION=$USER_SELECTED_LOCATION" >> $GITHUB_OUTPUT @@ -195,11 +368,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="docgen" # Account name as specified @@ -244,10 +419,12 @@ jobs: - name: Determine Docker Image Tag id: determine_image_tag + env: + INPUT_DOCKER_IMAGE_TAG: ${{ inputs.docker_image_tag }} run: | if [[ "${{ env.BUILD_DOCKER_IMAGE }}" == "true" ]]; then - if [[ -n "${{ inputs.docker_image_tag }}" ]]; then - IMAGE_TAG="${{ inputs.docker_image_tag }}" + if [[ -n "$INPUT_DOCKER_IMAGE_TAG" ]]; then + IMAGE_TAG="$INPUT_DOCKER_IMAGE_TAG" echo "🔗 Using Docker image tag from build job: $IMAGE_TAG" else echo "❌ Docker build job failed or was skipped, but BUILD_DOCKER_IMAGE is true" @@ -293,6 +470,9 @@ jobs: - name: Display Workflow Configuration to GitHub Summary shell: bash + env: + INPUT_AZURE_LOCATION: ${{ inputs.azure_location }} + INPUT_RESOURCE_GROUP_NAME: ${{ inputs.resource_group_name }} run: | echo "## 📋 Workflow Configuration Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY @@ -305,12 +485,12 @@ jobs: echo "| **Cleanup Resources** | ${{ env.CLEANUP_RESOURCES == 'true' && '✅ Yes' || '❌ No' }} |" >> $GITHUB_STEP_SUMMARY echo "| **Build Docker Image** | ${{ env.BUILD_DOCKER_IMAGE == 'true' && '✅ Yes' || '❌ No' }} |" >> $GITHUB_STEP_SUMMARY - if [[ "${{ inputs.trigger_type }}" == "workflow_dispatch" && -n "${{ inputs.azure_location }}" ]]; then - echo "| **Azure Location** | \`${{ inputs.azure_location }}\` (User Selected) |" >> $GITHUB_STEP_SUMMARY + if [[ "${{ inputs.trigger_type }}" == "workflow_dispatch" && -n "$INPUT_AZURE_LOCATION" ]]; then + echo "| **Azure Location** | \`$INPUT_AZURE_LOCATION\` (User Selected) |" >> $GITHUB_STEP_SUMMARY fi - if [[ -n "${{ inputs.resource_group_name }}" ]]; then - echo "| **Resource Group** | \`${{ inputs.resource_group_name }}\` (Pre-specified) |" >> $GITHUB_STEP_SUMMARY + if [[ -n "$INPUT_RESOURCE_GROUP_NAME" ]]; then + echo "| **Resource Group** | \`$INPUT_RESOURCE_GROUP_NAME\` (Pre-specified) |" >> $GITHUB_STEP_SUMMARY else echo "| **Resource Group** | \`${{ env.RESOURCE_GROUP_NAME }}\` (Auto-generated) |" >> $GITHUB_STEP_SUMMARY fi diff --git a/.github/workflows/job-send-notification.yml b/.github/workflows/job-send-notification.yml index 51fd82b4..29edb29f 100644 --- a/.github/workflows/job-send-notification.yml +++ b/.github/workflows/job-send-notification.yml @@ -75,18 +75,176 @@ jobs: env: accelerator_name: "DocGen" steps: + - name: Validate Workflow Input Parameters + shell: bash + env: + INPUT_TRIGGER_TYPE: ${{ inputs.trigger_type }} + INPUT_WAF_ENABLED: ${{ inputs.waf_enabled }} + INPUT_EXP: ${{ inputs.EXP }} + INPUT_RUN_E2E_TESTS: ${{ inputs.run_e2e_tests }} + INPUT_EXISTING_WEBAPP_URL: ${{ inputs.existing_webapp_url }} + INPUT_DEPLOY_RESULT: ${{ inputs.deploy_result }} + INPUT_E2E_TEST_RESULT: ${{ inputs.e2e_test_result }} + INPUT_WEB_APPURL: ${{ inputs.WEB_APPURL }} + INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }} + INPUT_QUOTA_FAILED: ${{ inputs.QUOTA_FAILED }} + INPUT_TEST_SUCCESS: ${{ inputs.TEST_SUCCESS }} + INPUT_TEST_REPORT_URL: ${{ inputs.TEST_REPORT_URL }} + run: | + echo "🔍 Validating workflow input parameters..." + VALIDATION_FAILED=false + + # Validate trigger_type (required - alphanumeric with underscores) + if [[ -z "$INPUT_TRIGGER_TYPE" ]]; then + echo "❌ ERROR: trigger_type is required but was not provided" + VALIDATION_FAILED=true + elif [[ ! "$INPUT_TRIGGER_TYPE" =~ ^[a-zA-Z0-9_]+$ ]]; then + echo "❌ ERROR: trigger_type '$INPUT_TRIGGER_TYPE' is invalid. Must contain only alphanumeric characters and underscores" + VALIDATION_FAILED=true + else + echo "✅ trigger_type: '$INPUT_TRIGGER_TYPE' is valid" + fi + + # Validate waf_enabled (boolean) + if [[ "$INPUT_WAF_ENABLED" != "true" && "$INPUT_WAF_ENABLED" != "false" ]]; then + echo "❌ ERROR: waf_enabled must be 'true' or 'false', got: '$INPUT_WAF_ENABLED'" + VALIDATION_FAILED=true + else + echo "✅ waf_enabled: '$INPUT_WAF_ENABLED' is valid" + fi + + # Validate EXP (boolean) + if [[ "$INPUT_EXP" != "true" && "$INPUT_EXP" != "false" ]]; then + echo "❌ ERROR: EXP must be 'true' or 'false', got: '$INPUT_EXP'" + VALIDATION_FAILED=true + else + echo "✅ EXP: '$INPUT_EXP' is valid" + fi + + # Validate run_e2e_tests (specific allowed values) + if [[ -n "$INPUT_RUN_E2E_TESTS" ]]; then + ALLOWED_VALUES=("None" "GoldenPath-Testing" "Smoke-Testing") + if [[ ! " ${ALLOWED_VALUES[@]} " =~ " ${INPUT_RUN_E2E_TESTS} " ]]; then + echo "❌ ERROR: run_e2e_tests '$INPUT_RUN_E2E_TESTS' is invalid. Allowed values: ${ALLOWED_VALUES[*]}" + VALIDATION_FAILED=true + else + echo "✅ run_e2e_tests: '$INPUT_RUN_E2E_TESTS' is valid" + fi + fi + + # Validate existing_webapp_url (must start with https if provided) + if [[ -n "$INPUT_EXISTING_WEBAPP_URL" ]]; then + if [[ ! "$INPUT_EXISTING_WEBAPP_URL" =~ ^https:// ]]; then + echo "❌ ERROR: existing_webapp_url must start with 'https://', got: '$INPUT_EXISTING_WEBAPP_URL'" + VALIDATION_FAILED=true + else + echo "✅ existing_webapp_url: '$INPUT_EXISTING_WEBAPP_URL' is valid" + fi + fi + + # Validate deploy_result (required, must be specific values) + if [[ -z "$INPUT_DEPLOY_RESULT" ]]; then + echo "❌ ERROR: deploy_result is required but not provided" + VALIDATION_FAILED=true + else + ALLOWED_DEPLOY_RESULTS=("success" "failure" "skipped") + if [[ ! " ${ALLOWED_DEPLOY_RESULTS[@]} " =~ " ${INPUT_DEPLOY_RESULT} " ]]; then + echo "❌ ERROR: deploy_result '$INPUT_DEPLOY_RESULT' is invalid. Allowed values: ${ALLOWED_DEPLOY_RESULTS[*]}" + VALIDATION_FAILED=true + else + echo "✅ deploy_result: '$INPUT_DEPLOY_RESULT' is valid" + fi + fi + + # Validate e2e_test_result (required, must be specific values) + if [[ -z "$INPUT_E2E_TEST_RESULT" ]]; then + echo "❌ ERROR: e2e_test_result is required but not provided" + VALIDATION_FAILED=true + else + ALLOWED_TEST_RESULTS=("success" "failure" "skipped") + if [[ ! " ${ALLOWED_TEST_RESULTS[@]} " =~ " ${INPUT_E2E_TEST_RESULT} " ]]; then + echo "❌ ERROR: e2e_test_result '$INPUT_E2E_TEST_RESULT' is invalid. Allowed values: ${ALLOWED_TEST_RESULTS[*]}" + VALIDATION_FAILED=true + else + echo "✅ e2e_test_result: '$INPUT_E2E_TEST_RESULT' is valid" + fi + fi + + # Validate WEB_APPURL (must start with https if provided) + if [[ -n "$INPUT_WEB_APPURL" ]]; then + if [[ ! "$INPUT_WEB_APPURL" =~ ^https:// ]]; then + echo "❌ ERROR: WEB_APPURL must start with 'https://', got: '$INPUT_WEB_APPURL'" + VALIDATION_FAILED=true + else + echo "✅ WEB_APPURL: '$INPUT_WEB_APPURL' is valid" + fi + fi + + # Validate RESOURCE_GROUP_NAME (Azure resource group naming convention if provided) + if [[ -n "$INPUT_RESOURCE_GROUP_NAME" ]]; then + if [[ ! "$INPUT_RESOURCE_GROUP_NAME" =~ ^[a-zA-Z0-9._\(\)-]+$ ]] || [[ "$INPUT_RESOURCE_GROUP_NAME" =~ \.$ ]]; then + echo "❌ ERROR: RESOURCE_GROUP_NAME '$INPUT_RESOURCE_GROUP_NAME' is invalid. Must contain only alphanumerics, periods, underscores, hyphens, and parentheses. Cannot end with period." + VALIDATION_FAILED=true + elif [[ ${#INPUT_RESOURCE_GROUP_NAME} -gt 90 ]]; then + echo "❌ ERROR: RESOURCE_GROUP_NAME '$INPUT_RESOURCE_GROUP_NAME' exceeds 90 characters" + VALIDATION_FAILED=true + else + echo "✅ RESOURCE_GROUP_NAME: '$INPUT_RESOURCE_GROUP_NAME' is valid" + fi + fi + + # Validate QUOTA_FAILED (must be 'true' 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_INPUT: ${{ inputs.run_e2e_tests }} run: | - if [ "${{ env.RUN_E2E_TESTS }}" = "GoldenPath-Testing" ]; then + if [ "$RUN_E2E_TESTS_INPUT" = "GoldenPath-Testing" ]; then TEST_SUITE_NAME="Golden Path Testing" - elif [ "${{ env.RUN_E2E_TESTS }}" = "Smoke-Testing" ]; then + elif [ "$RUN_E2E_TESTS_INPUT" = "Smoke-Testing" ]; then TEST_SUITE_NAME="Smoke Testing" - elif [ "${{ env.RUN_E2E_TESTS }}" = "None" ]; then + elif [ "$RUN_E2E_TESTS_INPUT" = "None" ]; then TEST_SUITE_NAME="None" else - TEST_SUITE_NAME="${{ env.RUN_E2E_TESTS }}" + TEST_SUITE_NAME="$RUN_E2E_TESTS_INPUT" fi echo "TEST_SUITE_NAME=$TEST_SUITE_NAME" >> $GITHUB_OUTPUT echo "Test Suite: $TEST_SUITE_NAME" @@ -94,6 +252,9 @@ jobs: - name: Send Quota Failure Notification if: inputs.deploy_result == 'failure' && inputs.QUOTA_FAILED == 'true' shell: bash + env: + INPUT_DEPLOY_RESULT: ${{ inputs.deploy_result }} + INPUT_QUOTA_FAILED: ${{ inputs.QUOTA_FAILED }} run: | RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" EMAIL_BODY=$(cat <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

", @@ -162,11 +335,19 @@ jobs: - name: Send Test Failure Notification if: inputs.deploy_result == 'success' && inputs.e2e_test_result != 'skipped' && inputs.TEST_SUCCESS != 'true' shell: bash + env: + INPUT_DEPLOY_RESULT: ${{ inputs.deploy_result }} + INPUT_E2E_TEST_RESULT: ${{ inputs.e2e_test_result }} + INPUT_TEST_SUCCESS: ${{ inputs.TEST_SUCCESS }} + INPUT_WEB_APPURL: ${{ inputs.WEB_APPURL }} + INPUT_EXISTING_WEBAPP_URL: ${{ inputs.existing_webapp_url }} + INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }} + INPUT_TEST_REPORT_URL: ${{ inputs.TEST_REPORT_URL }} run: | RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - TEST_REPORT_URL="${{ inputs.TEST_REPORT_URL }}" - WEBAPP_URL="${{ inputs.WEB_APPURL || inputs.existing_webapp_url }}" - RESOURCE_GROUP="${{ inputs.RESOURCE_GROUP_NAME }}" + TEST_REPORT_URL="$INPUT_TEST_REPORT_URL" + WEBAPP_URL="${INPUT_WEB_APPURL:-$INPUT_EXISTING_WEBAPP_URL}" + RESOURCE_GROUP="$INPUT_RESOURCE_GROUP_NAME" TEST_SUITE_NAME="${{ steps.test_suite.outputs.TEST_SUITE_NAME }}" EMAIL_BODY=$(cat < Date: Fri, 9 Jan 2026 14:08:10 +0530 Subject: [PATCH 2/3] Add Permissions --- .github/workflows/deploy-linux.yml | 4 +++- .github/workflows/deploy-orchestrator.yml | 5 ++++- .github/workflows/deploy-windows.yml | 5 ++++- .github/workflows/deploy.yml | 5 ++++- .github/workflows/docker-build-and-push.yml | 5 ++++- .github/workflows/job-cleanup-deployment.yml | 5 ++++- .github/workflows/job-deploy-linux.yml | 5 ++++- .github/workflows/job-deploy-windows.yml | 5 ++++- .github/workflows/job-deploy.yml | 5 ++++- .github/workflows/job-docker-build.yml | 5 ++++- .github/workflows/job-send-notification.yml | 5 ++++- .github/workflows/test-automation-v2.yml | 5 ++++- .github/workflows/test-automation.yml | 5 ++++- 13 files changed, 51 insertions(+), 13 deletions(-) diff --git a/.github/workflows/deploy-linux.yml b/.github/workflows/deploy-linux.yml index 3ddfc578..6500a402 100644 --- a/.github/workflows/deploy-linux.yml +++ b/.github/workflows/deploy-linux.yml @@ -93,7 +93,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 7281fa72..008fb891 100644 --- a/.github/workflows/deploy-orchestrator.yml +++ b/.github/workflows/deploy-orchestrator.yml @@ -64,7 +64,10 @@ on: env: AZURE_DEV_COLLECT_TELEMETRY: ${{ vars.AZURE_DEV_COLLECT_TELEMETRY }} - +permissions: + contents: read + actions: read + jobs: docker-build: uses: ./.github/workflows/job-docker-build.yml diff --git a/.github/workflows/deploy-windows.yml b/.github/workflows/deploy-windows.yml index 4cd93c4f..e0bb6c32 100644 --- a/.github/workflows/deploy-windows.yml +++ b/.github/workflows/deploy-windows.yml @@ -83,7 +83,10 @@ 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 24a7b8be..5ef88988 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -20,7 +20,10 @@ env: GPT_MIN_CAPACITY: 150 TEXT_EMBEDDING_MIN_CAPACITY: 80 BRANCH_NAME: ${{ github.event.workflow_run.head_branch || github.head_ref || github.ref_name }} - +permissions: + contents: read + actions: read + jobs: deploy: runs-on: ubuntu-latest diff --git a/.github/workflows/docker-build-and-push.yml b/.github/workflows/docker-build-and-push.yml index 65824120..ee11c63a 100644 --- a/.github/workflows/docker-build-and-push.yml +++ b/.github/workflows/docker-build-and-push.yml @@ -26,7 +26,10 @@ on: - '!src/tests/**' merge_group: workflow_dispatch: - +permissions: + contents: read + actions: read + jobs: build-and-push: runs-on: ubuntu-latest diff --git a/.github/workflows/job-cleanup-deployment.yml b/.github/workflows/job-cleanup-deployment.yml index 6b920a4e..8a6aa8ff 100644 --- a/.github/workflows/job-cleanup-deployment.yml +++ b/.github/workflows/job-cleanup-deployment.yml @@ -40,7 +40,10 @@ on: description: 'Docker Image Tag' required: true type: string - +permissions: + contents: read + actions: read + jobs: cleanup-deployment: runs-on: ${{ inputs.runner_os }} diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml index cad42669..d96b388f 100644 --- a/.github/workflows/job-deploy-linux.yml +++ b/.github/workflows/job-deploy-linux.yml @@ -38,7 +38,10 @@ on: WEB_APPURL: description: "Container Web App URL" value: ${{ jobs.deploy-linux.outputs.WEB_APPURL }} - +permissions: + contents: read + actions: read + jobs: deploy-linux: runs-on: ubuntu-latest diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml index 1855c858..bacb3626 100644 --- a/.github/workflows/job-deploy-windows.yml +++ b/.github/workflows/job-deploy-windows.yml @@ -38,7 +38,10 @@ on: WEB_APPURL: description: "Container Web App URL" value: ${{ jobs.deploy-windows.outputs.WEB_APPURL }} - +permissions: + contents: read + actions: read + jobs: deploy-windows: runs-on: windows-latest diff --git a/.github/workflows/job-deploy.yml b/.github/workflows/job-deploy.yml index 1a6f44ff..e3c586ec 100644 --- a/.github/workflows/job-deploy.yml +++ b/.github/workflows/job-deploy.yml @@ -98,7 +98,10 @@ env: CLEANUP_RESOURCES: ${{ inputs.trigger_type != 'workflow_dispatch' || inputs.cleanup_resources }} RUN_E2E_TESTS: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.run_e2e_tests || 'GoldenPath-Testing') || 'GoldenPath-Testing' }} BUILD_DOCKER_IMAGE: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.build_docker_image || false) || false }} - +permissions: + contents: read + actions: read + jobs: azure-setup: name: Azure Setup diff --git a/.github/workflows/job-docker-build.yml b/.github/workflows/job-docker-build.yml index 62956a43..fc564ea3 100644 --- a/.github/workflows/job-docker-build.yml +++ b/.github/workflows/job-docker-build.yml @@ -19,7 +19,10 @@ on: env: BRANCH_NAME: ${{ github.event.workflow_run.head_branch || github.head_ref || github.ref_name }} - +permissions: + contents: read + actions: read + jobs: docker-build: if: inputs.trigger_type == 'workflow_dispatch' && inputs.build_docker_image == true diff --git a/.github/workflows/job-send-notification.yml b/.github/workflows/job-send-notification.yml index 29edb29f..f917e3db 100644 --- a/.github/workflows/job-send-notification.yml +++ b/.github/workflows/job-send-notification.yml @@ -67,7 +67,10 @@ env: WAF_ENABLED: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.waf_enabled || false) || false }} EXP: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.EXP || false) || false }} RUN_E2E_TESTS: ${{ inputs.trigger_type == 'workflow_dispatch' && (inputs.run_e2e_tests || 'GoldenPath-Testing') || 'GoldenPath-Testing' }} - +permissions: + contents: read + actions: read + jobs: send-notification: runs-on: ubuntu-latest diff --git a/.github/workflows/test-automation-v2.yml b/.github/workflows/test-automation-v2.yml index e22712b5..637a79fa 100644 --- a/.github/workflows/test-automation-v2.yml +++ b/.github/workflows/test-automation-v2.yml @@ -24,7 +24,10 @@ env: url: ${{ inputs.DOCGEN_URL }} accelerator_name: "DocGen" 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 a2581b9d..9169caaa 100644 --- a/.github/workflows/test-automation.yml +++ b/.github/workflows/test-automation.yml @@ -22,7 +22,10 @@ on: env: url: ${{ inputs.DOCGEN_URL }} accelerator_name: "DocGen" - +permissions: + contents: read + actions: read + jobs: test: runs-on: ubuntu-latest From e5c5f8a138185e99857693b63ca573397513ab7c Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Fri, 9 Jan 2026 14:17:58 +0530 Subject: [PATCH 3/3] Remove export command and Curl Azure CLI setup steps and replace with Azure setup actions --- .github/workflows/deploy.yml | 26 +++++---------- .github/workflows/job-cleanup-deployment.yml | 8 ----- .github/workflows/job-deploy-linux.yml | 35 +++++++++----------- .github/workflows/job-deploy.yml | 19 ++++++----- 4 files changed, 34 insertions(+), 54 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5ef88988..d62c7bb5 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -34,26 +34,21 @@ 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: | az login --service-principal -u ${{ secrets.AZURE_CLIENT_ID }} -p ${{ secrets.AZURE_CLIENT_SECRET }} --tenant ${{ secrets.AZURE_TENANT_ID }} - name: Run Quota Check id: quota-check + env: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + GPT_MIN_CAPACITY: ${{ env.GPT_MIN_CAPACITY }} + TEXT_EMBEDDING_MIN_CAPACITY: ${{ env.TEXT_EMBEDDING_MIN_CAPACITY }} + AZURE_REGIONS: ${{ vars.AZURE_REGIONS }} run: | - export AZURE_CLIENT_ID=${{ secrets.AZURE_CLIENT_ID }} - export AZURE_TENANT_ID=${{ secrets.AZURE_TENANT_ID }} - export AZURE_CLIENT_SECRET=${{ secrets.AZURE_CLIENT_SECRET }} - export AZURE_SUBSCRIPTION_ID="${{ secrets.AZURE_SUBSCRIPTION_ID }}" - export GPT_MIN_CAPACITY=${{ env.GPT_MIN_CAPACITY }} - export TEXT_EMBEDDING_MIN_CAPACITY=${{ env.TEXT_EMBEDDING_MIN_CAPACITY }} - export AZURE_REGIONS="${{ vars.AZURE_REGIONS }}" - chmod +x scripts/checkquota.sh if ! scripts/checkquota.sh; then # If quota check fails due to insufficient quota, set the flag @@ -227,11 +222,6 @@ jobs: env: RESOURCE_GROUP_NAME: ${{ needs.deploy.outputs.RESOURCE_GROUP_NAME }} steps: - - name: Setup Azure CLI - run: | - curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash - az --version # Verify installation - - 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 8a6aa8ff..0e8aef42 100644 --- a/.github/workflows/job-cleanup-deployment.yml +++ b/.github/workflows/job-cleanup-deployment.yml @@ -55,14 +55,6 @@ jobs: ENV_NAME: ${{ inputs.ENV_NAME }} IMAGE_TAG: ${{ inputs.IMAGE_TAG }} steps: - - name: Setup Azure CLI - shell: bash - run: | - if [[ "${{ runner.os }}" == "Linux" ]]; then - curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash - fi - az --version - - name: Login to Azure shell: bash run: | diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml index d96b388f..562555e1 100644 --- a/.github/workflows/job-deploy-linux.yml +++ b/.github/workflows/job-deploy-linux.yml @@ -196,18 +196,9 @@ jobs: else echo "🔧 Configuring Non-WAF deployment - using default main.parameters.json..." fi - - - name: Setup Azure CLI - shell: bash - run: | - curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash - - name: Setup Azure Developer CLI (Linux) - if: runner.os == 'Linux' - shell: bash - run: | - curl -fsSL https://aka.ms/install-azd.sh | sudo bash - azd version + - name: Install azd + uses: Azure/setup-azd@v2 - name: Login to AZD id: login-azure @@ -298,26 +289,25 @@ jobs: fi # Extract values from azd output (adjust these based on actual output variable names) - export AI_FOUNDRY_RESOURCE_ID=$(echo "$DEPLOY_OUTPUT" | jq -r '.AI_FOUNDRY_RESOURCE_ID // empty') + AI_FOUNDRY_RESOURCE_ID=$(echo "$DEPLOY_OUTPUT" | jq -r '.AI_FOUNDRY_RESOURCE_ID // empty') echo "AI_FOUNDRY_RESOURCE_ID=$AI_FOUNDRY_RESOURCE_ID" >> $GITHUB_ENV - export AI_SEARCH_SERVICE_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.AI_SEARCH_SERVICE_NAME // empty') + AI_SEARCH_SERVICE_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.AI_SEARCH_SERVICE_NAME // empty') echo "AI_SEARCH_SERVICE_NAME=$AI_SEARCH_SERVICE_NAME" >> $GITHUB_ENV - export AZURE_COSMOSDB_ACCOUNT=$(echo "$DEPLOY_OUTPUT" | jq -r '.AZURE_COSMOSDB_ACCOUNT // empty') + AZURE_COSMOSDB_ACCOUNT=$(echo "$DEPLOY_OUTPUT" | jq -r '.AZURE_COSMOSDB_ACCOUNT // empty') echo "AZURE_COSMOSDB_ACCOUNT=$AZURE_COSMOSDB_ACCOUNT" >> $GITHUB_ENV - export STORAGE_ACCOUNT_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.STORAGE_ACCOUNT_NAME // empty') + STORAGE_ACCOUNT_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.STORAGE_ACCOUNT_NAME // empty') echo "STORAGE_ACCOUNT_NAME=$STORAGE_ACCOUNT_NAME" >> $GITHUB_ENV - export STORAGE_CONTAINER_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.STORAGE_CONTAINER_NAME // empty') + STORAGE_CONTAINER_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.STORAGE_CONTAINER_NAME // empty') echo "STORAGE_CONTAINER_NAME=$STORAGE_CONTAINER_NAME" >> $GITHUB_ENV - export KEY_VAULT_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.KEY_VAULT_NAME // empty') + KEY_VAULT_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.KEY_VAULT_NAME // empty') echo "KEY_VAULT_NAME=$KEY_VAULT_NAME" >> $GITHUB_ENV - export RESOURCE_GROUP_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.RESOURCE_GROUP_NAME // .AZURE_RESOURCE_GROUP // empty') - [[ -z "$RESOURCE_GROUP_NAME" ]] && export RESOURCE_GROUP_NAME="$RESOURCE_GROUP_NAME" + RESOURCE_GROUP_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.RESOURCE_GROUP_NAME // empty') echo "RESOURCE_GROUP_NAME=$RESOURCE_GROUP_NAME" >> $GITHUB_ENV WEB_APPURL=$(echo "$DEPLOY_OUTPUT" | jq -r '.WEB_APP_URL // .SERVICE_BACKEND_ENDPOINT_URL // empty') @@ -329,6 +319,13 @@ jobs: env: AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + STORAGE_ACCOUNT_NAME: ${{ env.STORAGE_ACCOUNT_NAME }} + STORAGE_CONTAINER_NAME: ${{ env.STORAGE_CONTAINER_NAME }} + KEY_VAULT_NAME: ${{ env.KEY_VAULT_NAME }} + AZURE_COSMOSDB_ACCOUNT: ${{ env.AZURE_COSMOSDB_ACCOUNT }} + RESOURCE_GROUP_NAME: ${{ env.RESOURCE_GROUP_NAME }} + AI_SEARCH_SERVICE_NAME: ${{ env.AI_SEARCH_SERVICE_NAME }} + AI_FOUNDRY_RESOURCE_ID: ${{ env.AI_FOUNDRY_RESOURCE_ID }} run: | set -e az account set --subscription "$AZURE_SUBSCRIPTION_ID" diff --git a/.github/workflows/job-deploy.yml b/.github/workflows/job-deploy.yml index e3c586ec..d9ca1163 100644 --- a/.github/workflows/job-deploy.yml +++ b/.github/workflows/job-deploy.yml @@ -317,21 +317,22 @@ jobs: - name: Run Quota Check id: quota-check + env: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + GPT_MIN_CAPACITY: ${{ env.GPT_MIN_CAPACITY }} + TEXT_EMBEDDING_MIN_CAPACITY: ${{ env.TEXT_EMBEDDING_MIN_CAPACITY }} + AZURE_REGIONS: ${{ vars.AZURE_REGIONS }} run: | - export AZURE_CLIENT_ID=${{ secrets.AZURE_CLIENT_ID }} - export AZURE_TENANT_ID=${{ secrets.AZURE_TENANT_ID }} - export AZURE_CLIENT_SECRET=${{ secrets.AZURE_CLIENT_SECRET }} - export AZURE_SUBSCRIPTION_ID="${{ secrets.AZURE_SUBSCRIPTION_ID }}" - export GPT_MIN_CAPACITY=${{ env.GPT_MIN_CAPACITY }} - export TEXT_EMBEDDING_MIN_CAPACITY=${{ env.TEXT_EMBEDDING_MIN_CAPACITY }} - export AZURE_REGIONS="${{ vars.AZURE_REGIONS }}" - chmod +x scripts/checkquota.sh if ! scripts/checkquota.sh; then + # If quota check fails due to insufficient quota, set the flag if grep -q "No region with sufficient quota found" scripts/checkquota.sh; then echo "QUOTA_FAILED=true" >> $GITHUB_ENV fi - exit 1 + exit 1 # Fail the pipeline if any other failure occurs fi - name: Set Quota Failure Output