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