deploy-azd-core (reusable) #51
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: deploy-azd | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| environment: | |
| description: Deployment environment name (for azd -e) | |
| required: true | |
| default: dev | |
| location: | |
| description: Azure location | |
| required: true | |
| default: centralus | |
| projectName: | |
| description: Project prefix used by naming convention | |
| required: true | |
| default: holidaypeakhub | |
| imageTag: | |
| description: Image tag to deploy | |
| required: true | |
| default: latest | |
| deployStatic: | |
| description: Provision static web app resources | |
| required: true | |
| type: boolean | |
| default: true | |
| uiOnly: | |
| description: Deploy only the UI using SWA token (skips provision and Azure login jobs) | |
| required: true | |
| type: boolean | |
| default: false | |
| apiBaseUrl: | |
| description: Optional API base URL for UI build override | |
| required: false | |
| default: '' | |
| seedDemoData: | |
| description: Run demo faker seed job after deployment (non-prod only) | |
| required: true | |
| type: boolean | |
| default: true | |
| deployChangedOnly: | |
| description: Legacy flag (changed-app-only deployment is always enforced) | |
| required: true | |
| type: boolean | |
| default: true | |
| permissions: | |
| id-token: write | |
| contents: read | |
| jobs: | |
| detect-changes: | |
| if: ${{ !inputs.uiOnly }} | |
| runs-on: ubuntu-latest | |
| outputs: | |
| crud_changed: ${{ steps.detect.outputs.crud_changed }} | |
| ui_changed: ${{ steps.detect.outputs.ui_changed }} | |
| agents_changed: ${{ steps.detect.outputs.agents_changed }} | |
| changed_agents_matrix: ${{ steps.detect.outputs.changed_agents_matrix }} | |
| changed_agent_services_csv: ${{ steps.detect.outputs.changed_agent_services_csv }} | |
| changed_aks_services_csv: ${{ steps.detect.outputs.changed_aks_services_csv }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Detect changed services | |
| id: detect | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| DEFAULT_BRANCH="${{ github.event.repository.default_branch }}" | |
| git fetch origin "$DEFAULT_BRANCH" --depth=1 | |
| CHANGED_FILES=$(git diff --name-only "origin/$DEFAULT_BRANCH...HEAD") | |
| mapfile -t AGENT_SERVICES < <(python3 - <<'PY' | |
| import re | |
| with open('azure.yaml', encoding='utf-8') as f: | |
| lines = f.readlines() | |
| in_services = False | |
| current_service = None | |
| current_host = None | |
| services = [] | |
| for raw in lines: | |
| line = raw.rstrip('\n') | |
| if not in_services: | |
| if re.match(r'^services:\s*$', line): | |
| in_services = True | |
| continue | |
| if re.match(r'^[^\s]', line): | |
| break | |
| service_match = re.match(r'^ ([a-z0-9\-]+):\s*$', line) | |
| if service_match: | |
| if current_service and current_host == 'aks' and current_service != 'crud-service': | |
| services.append(current_service) | |
| current_service = service_match.group(1) | |
| current_host = None | |
| continue | |
| host_match = re.match(r'^ host:\s*(\S+)\s*$', line) | |
| if host_match: | |
| current_host = host_match.group(1) | |
| if current_service and current_host == 'aks' and current_service != 'crud-service': | |
| services.append(current_service) | |
| for service in services: | |
| print(service) | |
| PY | |
| ) | |
| CRUD_CHANGED=false | |
| if echo "$CHANGED_FILES" | grep -Eq '^apps/crud-service/'; then | |
| CRUD_CHANGED=true | |
| fi | |
| UI_CHANGED=false | |
| if echo "$CHANGED_FILES" | grep -Eq '^apps/ui/'; then | |
| UI_CHANGED=true | |
| fi | |
| AGENTS_CHANGED=false | |
| MATRIX='[]' | |
| CHANGED_AGENTS=() | |
| for service in "${AGENT_SERVICES[@]}"; do | |
| if echo "$CHANGED_FILES" | grep -Eq "^apps/$service/"; then | |
| CHANGED_AGENTS+=("$service") | |
| fi | |
| done | |
| if [ ${#CHANGED_AGENTS[@]} -gt 0 ]; then | |
| AGENTS_CHANGED=true | |
| MATRIX=$(printf '%s\n' "${CHANGED_AGENTS[@]}" | jq -R . | jq -s 'map({service: .})') | |
| fi | |
| CHANGED_AGENT_SERVICES_CSV="" | |
| if [ ${#CHANGED_AGENTS[@]} -gt 0 ]; then | |
| CHANGED_AGENT_SERVICES_CSV=$(IFS=,; echo "${CHANGED_AGENTS[*]}") | |
| fi | |
| CHANGED_AKS_SERVICES_CSV="$CHANGED_AGENT_SERVICES_CSV" | |
| if [ "$CRUD_CHANGED" = true ]; then | |
| if [ -n "$CHANGED_AKS_SERVICES_CSV" ]; then | |
| CHANGED_AKS_SERVICES_CSV="crud-service,$CHANGED_AKS_SERVICES_CSV" | |
| else | |
| CHANGED_AKS_SERVICES_CSV="crud-service" | |
| fi | |
| fi | |
| echo "crud_changed=$CRUD_CHANGED" >> "$GITHUB_OUTPUT" | |
| echo "ui_changed=$UI_CHANGED" >> "$GITHUB_OUTPUT" | |
| echo "agents_changed=$AGENTS_CHANGED" >> "$GITHUB_OUTPUT" | |
| echo "changed_agents_matrix=$MATRIX" >> "$GITHUB_OUTPUT" | |
| echo "changed_agent_services_csv=$CHANGED_AGENT_SERVICES_CSV" >> "$GITHUB_OUTPUT" | |
| echo "changed_aks_services_csv=$CHANGED_AKS_SERVICES_CSV" >> "$GITHUB_OUTPUT" | |
| provision: | |
| if: ${{ !inputs.uiOnly }} | |
| needs: detect-changes | |
| runs-on: ubuntu-latest | |
| environment: ${{ inputs.environment }} | |
| outputs: | |
| AI_SERVICES_NAME: ${{ steps.outputs.outputs.AI_SERVICES_NAME }} | |
| PROJECT_ENDPOINT: ${{ steps.outputs.outputs.PROJECT_ENDPOINT }} | |
| PROJECT_NAME: ${{ steps.outputs.outputs.PROJECT_NAME }} | |
| COSMOS_ACCOUNT_URI: ${{ steps.outputs.outputs.COSMOS_ACCOUNT_URI }} | |
| COSMOS_DATABASE: ${{ steps.outputs.outputs.COSMOS_DATABASE }} | |
| KEY_VAULT_URI: ${{ steps.outputs.outputs.KEY_VAULT_URI }} | |
| REDIS_HOST: ${{ steps.outputs.outputs.REDIS_HOST }} | |
| EVENT_HUB_NAMESPACE: ${{ steps.outputs.outputs.EVENT_HUB_NAMESPACE }} | |
| APPLICATIONINSIGHTS_CONNECTION_STRING: ${{ steps.outputs.outputs.APPLICATIONINSIGHTS_CONNECTION_STRING }} | |
| POSTGRES_HOST: ${{ steps.outputs.outputs.POSTGRES_HOST }} | |
| POSTGRES_USER: ${{ steps.outputs.outputs.POSTGRES_USER }} | |
| POSTGRES_DATABASE: ${{ steps.outputs.outputs.POSTGRES_DATABASE }} | |
| env: | |
| AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Azure login (OIDC) | |
| uses: azure/login@v2 | |
| with: | |
| client-id: ${{ env.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ env.AZURE_TENANT_ID }} | |
| subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }} | |
| - name: Setup azd | |
| uses: Azure/setup-azd@v2 | |
| - name: Authenticate azd (OIDC) | |
| run: | | |
| azd auth login \ | |
| --client-id "${AZURE_CLIENT_ID}" \ | |
| --tenant-id "${AZURE_TENANT_ID}" \ | |
| --federated-credential-provider github \ | |
| --no-prompt | |
| - name: Configure azd environment context | |
| run: | | |
| azd env set AZURE_SUBSCRIPTION_ID "${AZURE_SUBSCRIPTION_ID}" -e "${{ inputs.environment }}" | |
| azd env set AZURE_LOCATION "${{ inputs.location }}" -e "${{ inputs.environment }}" | |
| azd env set AZURE_ENV_NAME "${{ inputs.environment }}" -e "${{ inputs.environment }}" | |
| azd env set AZURE_RESOURCE_GROUP "${{ inputs.projectName }}-${{ inputs.environment }}-rg" -e "${{ inputs.environment }}" | |
| azd env set resourceGroupName "${{ inputs.projectName }}-${{ inputs.environment }}-rg" -e "${{ inputs.environment }}" | |
| azd env set AZURE_AKS_CLUSTER_NAME "${{ inputs.projectName }}-${{ inputs.environment }}-aks" -e "${{ inputs.environment }}" | |
| azd env set AKS_CLUSTER_NAME "${{ inputs.projectName }}-${{ inputs.environment }}-aks" -e "${{ inputs.environment }}" | |
| azd env set AZURE_CONTAINER_REGISTRY_ENDPOINT "${{ inputs.projectName }}${{ inputs.environment }}acr.azurecr.io" -e "${{ inputs.environment }}" | |
| - name: Install kubelogin | |
| run: | | |
| for attempt in 1 2 3 4 5; do | |
| if az aks install-cli --kubelogin-version latest; then | |
| exit 0 | |
| fi | |
| echo "kubelogin install failed on attempt ${attempt}; retrying..." | |
| sleep 15 | |
| done | |
| echo "kubelogin install failed after retries" | |
| exit 1 | |
| - name: Ensure hook scripts executable | |
| run: chmod +x ./.infra/azd/hooks/*.sh | |
| - name: Ensure AKS cluster is running | |
| run: | | |
| AKS_RG="${{ inputs.projectName }}-${{ inputs.environment }}-rg" | |
| AKS_NAME="${{ inputs.projectName }}-${{ inputs.environment }}-aks" | |
| if az aks show --resource-group "$AKS_RG" --name "$AKS_NAME" >/dev/null 2>&1; then | |
| POWER_STATE=$(az aks show --resource-group "$AKS_RG" --name "$AKS_NAME" --query "powerState.code" -o tsv 2>/dev/null || true) | |
| if [ "$POWER_STATE" = "Stopped" ]; then | |
| echo "AKS cluster is stopped. Starting $AKS_NAME..." | |
| az aks start --resource-group "$AKS_RG" --name "$AKS_NAME" | |
| for _ in $(seq 1 40); do | |
| CURRENT_STATE=$(az aks show --resource-group "$AKS_RG" --name "$AKS_NAME" --query "powerState.code" -o tsv 2>/dev/null || true) | |
| if [ "$CURRENT_STATE" = "Running" ]; then | |
| echo "AKS cluster is running." | |
| break | |
| fi | |
| sleep 30 | |
| done | |
| fi | |
| else | |
| echo "AKS cluster does not exist yet. Continuing." | |
| fi | |
| - name: Install kubelogin | |
| run: | | |
| for attempt in 1 2 3 4 5; do | |
| if az aks install-cli --kubelogin-version latest; then | |
| exit 0 | |
| fi | |
| echo "kubelogin install failed on attempt ${attempt}; retrying..." | |
| sleep 15 | |
| done | |
| echo "kubelogin install failed after retries" | |
| exit 1 | |
| - name: Ensure hook scripts executable | |
| run: chmod +x ./.infra/azd/hooks/*.sh | |
| - name: Configure azd environment | |
| run: | | |
| azd env set AZURE_SUBSCRIPTION_ID "${AZURE_SUBSCRIPTION_ID}" -e "${{ inputs.environment }}" | |
| azd env set AZURE_LOCATION "${{ inputs.location }}" -e "${{ inputs.environment }}" | |
| azd env set AZURE_ENV_NAME "${{ inputs.environment }}" -e "${{ inputs.environment }}" | |
| azd env set AZURE_RESOURCE_GROUP "${{ inputs.projectName }}-${{ inputs.environment }}-rg" -e "${{ inputs.environment }}" | |
| azd env set resourceGroupName "${{ inputs.projectName }}-${{ inputs.environment }}-rg" -e "${{ inputs.environment }}" | |
| azd env set AZURE_AKS_CLUSTER_NAME "${{ inputs.projectName }}-${{ inputs.environment }}-aks" -e "${{ inputs.environment }}" | |
| azd env set AKS_CLUSTER_NAME "${{ inputs.projectName }}-${{ inputs.environment }}-aks" -e "${{ inputs.environment }}" | |
| azd env set AZURE_CONTAINER_REGISTRY_ENDPOINT "${{ inputs.projectName }}${{ inputs.environment }}acr.azurecr.io" -e "${{ inputs.environment }}" | |
| azd env set deployShared true -e "${{ inputs.environment }}" | |
| azd env set deployStatic "${{ inputs.deployStatic }}" -e "${{ inputs.environment }}" | |
| azd env set environment "${{ inputs.environment }}" -e "${{ inputs.environment }}" | |
| azd env set location "${{ inputs.location }}" -e "${{ inputs.environment }}" | |
| azd env set projectName "${{ inputs.projectName }}" -e "${{ inputs.environment }}" | |
| azd env set IMAGE_PREFIX "ghcr.io/${{ github.repository_owner }}" -e "${{ inputs.environment }}" | |
| azd env set IMAGE_TAG "${{ inputs.imageTag }}" -e "${{ inputs.environment }}" | |
| azd env set K8S_NAMESPACE holiday-peak -e "${{ inputs.environment }}" | |
| azd env set KEDA_ENABLED false -e "${{ inputs.environment }}" | |
| - name: Preflight drift remediation (non-prod) | |
| if: ${{ inputs.environment != 'prod' && inputs.environment != 'production' }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| RG_NAME="${{ inputs.projectName }}-${{ inputs.environment }}-rg" | |
| KEY_VAULT_NAME="${{ inputs.projectName }}-${{ inputs.environment }}-kv" | |
| POSTGRES_SERVER_NAME="${{ inputs.projectName }}-${{ inputs.environment }}-postgres" | |
| DESIRED_LOCATION="$(echo "${{ inputs.location }}" | tr '[:upper:]' '[:lower:]')" | |
| echo "Running non-prod preflight drift remediation for environment '${{ inputs.environment }}'." | |
| # Remediate soft-deleted Key Vault name conflicts that can block azd provision. | |
| if az keyvault show --name "$KEY_VAULT_NAME" --resource-group "$RG_NAME" >/dev/null 2>&1; then | |
| CURRENT_KV_LOCATION=$(az keyvault show \ | |
| --name "$KEY_VAULT_NAME" \ | |
| --resource-group "$RG_NAME" \ | |
| --query location -o tsv | tr '[:upper:]' '[:lower:]') | |
| if [ "$CURRENT_KV_LOCATION" != "$DESIRED_LOCATION" ]; then | |
| LOCATION_SUFFIX=$(echo "$DESIRED_LOCATION" | tr -cd '[:alnum:]' | cut -c1-4) | |
| KEY_VAULT_OVERRIDE=$(echo "${{ inputs.projectName }}-${{ inputs.environment }}-kv-${LOCATION_SUFFIX}" | tr '[:upper:]' '[:lower:]' | cut -c1-24) | |
| KEY_VAULT_OVERRIDE="${KEY_VAULT_OVERRIDE%-}" | |
| echo "Key Vault location mismatch detected for $KEY_VAULT_NAME ($CURRENT_KV_LOCATION vs $DESIRED_LOCATION)." | |
| echo "Setting keyVaultNameOverride to $KEY_VAULT_OVERRIDE for this deployment." | |
| azd env set keyVaultNameOverride "$KEY_VAULT_OVERRIDE" -e "${{ inputs.environment }}" | |
| else | |
| echo "Key Vault $KEY_VAULT_NAME already exists in $RG_NAME with matching location $CURRENT_KV_LOCATION." | |
| fi | |
| else | |
| DELETED_KV_LOCATION=$(az keyvault list-deleted \ | |
| --query "[?name=='$KEY_VAULT_NAME'] | [0].properties.location" \ | |
| -o tsv) | |
| if [ -n "$DELETED_KV_LOCATION" ]; then | |
| echo "Recovering soft-deleted Key Vault $KEY_VAULT_NAME." | |
| if az keyvault recover --name "$KEY_VAULT_NAME" >/dev/null 2>&1; then | |
| echo "Recovery initiated for $KEY_VAULT_NAME." | |
| else | |
| echo "Recovery failed for $KEY_VAULT_NAME. Attempting purge fallback from $DELETED_KV_LOCATION." | |
| az keyvault purge --name "$KEY_VAULT_NAME" --location "$DELETED_KV_LOCATION" | |
| fi | |
| for _ in $(seq 1 20); do | |
| if az keyvault show --name "$KEY_VAULT_NAME" --resource-group "$RG_NAME" >/dev/null 2>&1; then | |
| echo "Key Vault $KEY_VAULT_NAME is now available in $RG_NAME." | |
| break | |
| fi | |
| sleep 10 | |
| done | |
| else | |
| echo "No soft-deleted Key Vault conflict found for $KEY_VAULT_NAME." | |
| fi | |
| fi | |
| # Start stopped PostgreSQL Flexible Server so provisioning can reconcile state. | |
| if az postgres flexible-server show --resource-group "$RG_NAME" --name "$POSTGRES_SERVER_NAME" >/dev/null 2>&1; then | |
| POSTGRES_STATE=$(az postgres flexible-server show \ | |
| --resource-group "$RG_NAME" \ | |
| --name "$POSTGRES_SERVER_NAME" \ | |
| --query state -o tsv) | |
| if [ "$POSTGRES_STATE" = "Stopped" ]; then | |
| echo "PostgreSQL server $POSTGRES_SERVER_NAME is stopped. Starting..." | |
| az postgres flexible-server start --resource-group "$RG_NAME" --name "$POSTGRES_SERVER_NAME" | |
| POSTGRES_READY=false | |
| for _ in $(seq 1 40); do | |
| CURRENT_STATE=$(az postgres flexible-server show \ | |
| --resource-group "$RG_NAME" \ | |
| --name "$POSTGRES_SERVER_NAME" \ | |
| --query state -o tsv) | |
| if [ "$CURRENT_STATE" = "Ready" ]; then | |
| echo "PostgreSQL server is ready." | |
| POSTGRES_READY=true | |
| break | |
| fi | |
| sleep 15 | |
| done | |
| if [ "$POSTGRES_READY" != "true" ]; then | |
| echo "PostgreSQL server $POSTGRES_SERVER_NAME did not reach Ready state in time." | |
| exit 1 | |
| fi | |
| else | |
| echo "PostgreSQL server $POSTGRES_SERVER_NAME state is $POSTGRES_STATE. No start needed." | |
| fi | |
| else | |
| echo "PostgreSQL server $POSTGRES_SERVER_NAME does not exist yet. Continuing." | |
| fi | |
| - name: Provision infrastructure | |
| run: azd provision --no-prompt -e "${{ inputs.environment }}" | |
| - name: Ensure AKS identity can read Key Vault secrets | |
| run: | | |
| AKS_RG="${{ inputs.projectName }}-${{ inputs.environment }}-rg" | |
| AKS_NAME="${{ inputs.projectName }}-${{ inputs.environment }}-aks" | |
| KEY_VAULT_NAME="${{ inputs.projectName }}-${{ inputs.environment }}-kv" | |
| KUBELET_OBJECT_ID=$(az aks show \ | |
| --resource-group "$AKS_RG" \ | |
| --name "$AKS_NAME" \ | |
| --query "identityProfile.kubeletidentity.objectId" -o tsv) | |
| KEY_VAULT_ID=$(az keyvault show \ | |
| --resource-group "$AKS_RG" \ | |
| --name "$KEY_VAULT_NAME" \ | |
| --query id -o tsv) | |
| EXISTING=$(az role assignment list \ | |
| --assignee-object-id "$KUBELET_OBJECT_ID" \ | |
| --scope "$KEY_VAULT_ID" \ | |
| --query "[?roleDefinitionName=='Key Vault Secrets User'] | length(@)" -o tsv) | |
| if [ "$EXISTING" = "0" ]; then | |
| az role assignment create \ | |
| --assignee-object-id "$KUBELET_OBJECT_ID" \ | |
| --assignee-principal-type ServicePrincipal \ | |
| --role "Key Vault Secrets User" \ | |
| --scope "$KEY_VAULT_ID" | |
| fi | |
| - name: Export provisioned outputs | |
| id: outputs | |
| run: | | |
| eval "$(azd env get-values -e '${{ inputs.environment }}' | sed 's/^/export /')" | |
| { | |
| echo "AI_SERVICES_NAME=${AI_SERVICES_NAME}" | |
| echo "PROJECT_ENDPOINT=${PROJECT_ENDPOINT}" | |
| echo "PROJECT_NAME=${PROJECT_NAME}" | |
| echo "COSMOS_ACCOUNT_URI=${COSMOS_ACCOUNT_URI}" | |
| echo "COSMOS_DATABASE=${COSMOS_DATABASE}" | |
| echo "KEY_VAULT_URI=${KEY_VAULT_URI}" | |
| echo "REDIS_HOST=${REDIS_HOST}" | |
| echo "EVENT_HUB_NAMESPACE=${EVENT_HUB_NAMESPACE}" | |
| echo "APPLICATIONINSIGHTS_CONNECTION_STRING=${APPLICATIONINSIGHTS_CONNECTION_STRING}" | |
| echo "POSTGRES_HOST=${POSTGRES_HOST}" | |
| echo "POSTGRES_USER=${POSTGRES_USER}" | |
| echo "POSTGRES_DATABASE=${POSTGRES_DATABASE}" | |
| } >> "$GITHUB_OUTPUT" | |
| deploy-foundry-models: | |
| runs-on: ubuntu-latest | |
| needs: provision | |
| environment: ${{ inputs.environment }} | |
| env: | |
| AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Azure login (OIDC) | |
| uses: azure/login@v2 | |
| with: | |
| client-id: ${{ env.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ env.AZURE_TENANT_ID }} | |
| subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }} | |
| - name: Deploy AI model deployments | |
| run: | | |
| bash .infra/azd/hooks/deploy-foundry-models.sh \ | |
| "${{ inputs.projectName }}-${{ inputs.environment }}-rg" \ | |
| "${{ needs.provision.outputs.AI_SERVICES_NAME }}" | |
| env: | |
| AZURE_RESOURCE_GROUP: ${{ inputs.projectName }}-${{ inputs.environment }}-rg | |
| AI_SERVICES_NAME: ${{ needs.provision.outputs.AI_SERVICES_NAME }} | |
| deploy-crud: | |
| runs-on: ubuntu-latest | |
| needs: | |
| - provision | |
| - detect-changes | |
| if: ${{ !inputs.uiOnly && needs.detect-changes.outputs.crud_changed == 'true' }} | |
| environment: ${{ inputs.environment }} | |
| env: | |
| AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Azure login (OIDC) | |
| uses: azure/login@v2 | |
| with: | |
| client-id: ${{ env.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ env.AZURE_TENANT_ID }} | |
| subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }} | |
| - name: Setup azd | |
| uses: Azure/setup-azd@v2 | |
| - name: Authenticate azd (OIDC) | |
| run: | | |
| azd auth login \ | |
| --client-id "${AZURE_CLIENT_ID}" \ | |
| --tenant-id "${AZURE_TENANT_ID}" \ | |
| --federated-credential-provider github \ | |
| --no-prompt | |
| - name: Configure azd environment context | |
| run: | | |
| azd env set AZURE_SUBSCRIPTION_ID "${AZURE_SUBSCRIPTION_ID}" -e "${{ inputs.environment }}" | |
| azd env set AZURE_LOCATION "${{ inputs.location }}" -e "${{ inputs.environment }}" | |
| azd env set AZURE_ENV_NAME "${{ inputs.environment }}" -e "${{ inputs.environment }}" | |
| azd env set AZURE_RESOURCE_GROUP "${{ inputs.projectName }}-${{ inputs.environment }}-rg" -e "${{ inputs.environment }}" | |
| azd env set resourceGroupName "${{ inputs.projectName }}-${{ inputs.environment }}-rg" -e "${{ inputs.environment }}" | |
| azd env set AZURE_AKS_CLUSTER_NAME "${{ inputs.projectName }}-${{ inputs.environment }}-aks" -e "${{ inputs.environment }}" | |
| azd env set AKS_CLUSTER_NAME "${{ inputs.projectName }}-${{ inputs.environment }}-aks" -e "${{ inputs.environment }}" | |
| azd env set AZURE_CONTAINER_REGISTRY_ENDPOINT "${{ inputs.projectName }}${{ inputs.environment }}acr.azurecr.io" -e "${{ inputs.environment }}" | |
| - name: Install kubelogin | |
| run: | | |
| for attempt in 1 2 3 4 5; do | |
| if az aks install-cli --kubelogin-version latest; then | |
| exit 0 | |
| fi | |
| echo "kubelogin install failed on attempt ${attempt}; retrying..." | |
| sleep 15 | |
| done | |
| echo "kubelogin install failed after retries" | |
| exit 1 | |
| - name: Ensure hook scripts executable | |
| run: chmod +x ./.infra/azd/hooks/*.sh | |
| - name: Configure azd environment context | |
| run: | | |
| azd env set AZURE_SUBSCRIPTION_ID "${AZURE_SUBSCRIPTION_ID}" -e "${{ inputs.environment }}" | |
| azd env set AZURE_LOCATION "${{ inputs.location }}" -e "${{ inputs.environment }}" | |
| azd env set AZURE_ENV_NAME "${{ inputs.environment }}" -e "${{ inputs.environment }}" | |
| azd env set AZURE_RESOURCE_GROUP "${{ inputs.projectName }}-${{ inputs.environment }}-rg" -e "${{ inputs.environment }}" | |
| azd env set resourceGroupName "${{ inputs.projectName }}-${{ inputs.environment }}-rg" -e "${{ inputs.environment }}" | |
| azd env set AZURE_AKS_CLUSTER_NAME "${{ inputs.projectName }}-${{ inputs.environment }}-aks" -e "${{ inputs.environment }}" | |
| azd env set AKS_CLUSTER_NAME "${{ inputs.projectName }}-${{ inputs.environment }}-aks" -e "${{ inputs.environment }}" | |
| azd env set AZURE_CONTAINER_REGISTRY_ENDPOINT "${{ inputs.projectName }}${{ inputs.environment }}acr.azurecr.io" -e "${{ inputs.environment }}" | |
| - name: Get AKS credentials | |
| run: | | |
| az aks get-credentials \ | |
| --resource-group "${{ inputs.projectName }}-${{ inputs.environment }}-rg" \ | |
| --name "${{ inputs.projectName }}-${{ inputs.environment }}-aks" \ | |
| --overwrite-existing | |
| - name: Resolve AKS managed identity client ID | |
| run: | | |
| AKS_MI_CLIENT_ID=$(az aks show \ | |
| --resource-group "${{ inputs.projectName }}-${{ inputs.environment }}-rg" \ | |
| --name "${{ inputs.projectName }}-${{ inputs.environment }}-aks" \ | |
| --query "identityProfile.kubeletidentity.clientId" -o tsv) | |
| echo "WORKLOAD_AZURE_CLIENT_ID=${AKS_MI_CLIENT_ID}" >> "$GITHUB_ENV" | |
| - name: Resolve ingress class | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| kubectl get ingressclass -o wide || true | |
| if [ -n "${INGRESS_CLASS_NAME:-}" ] && kubectl get ingressclass "${INGRESS_CLASS_NAME}" >/dev/null 2>&1; then | |
| echo "Using preconfigured ingress class: ${INGRESS_CLASS_NAME}" | |
| echo "INGRESS_CLASS_NAME=${INGRESS_CLASS_NAME}" >> "$GITHUB_ENV" | |
| exit 0 | |
| fi | |
| for cls in webapprouting.kubernetes.azure.com nginx azure-application-gateway; do | |
| if kubectl get ingressclass "$cls" >/dev/null 2>&1; then | |
| echo "Using detected ingress class: $cls" | |
| echo "INGRESS_CLASS_NAME=$cls" >> "$GITHUB_ENV" | |
| exit 0 | |
| fi | |
| done | |
| echo "No supported IngressClass found. Enable AKS Web App Routing or provide INGRESS_CLASS_NAME." >&2 | |
| exit 1 | |
| - name: Deploy CRUD service | |
| timeout-minutes: 25 | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| max_attempts=4 | |
| for attempt in $(seq 1 "$max_attempts"); do | |
| echo "Deploy attempt ${attempt}/${max_attempts}" | |
| if azd deploy --service crud-service --no-prompt -e "${{ inputs.environment }}"; then | |
| echo "CRUD deploy succeeded." | |
| exit 0 | |
| fi | |
| echo "Attempt ${attempt} failed; collecting ingress diagnostics..." | |
| kubectl get ingressclass -o wide || true | |
| kubectl get ingress -n holiday-peak -o wide || true | |
| kubectl get pods -n app-routing-system -o wide || true | |
| kubectl get pods -n ingress-nginx -o wide || true | |
| if [ "$attempt" -eq "$max_attempts" ]; then | |
| echo "CRUD deploy failed after ${max_attempts} attempts." >&2 | |
| exit 1 | |
| fi | |
| backoff=$((attempt * 45)) | |
| echo "Retrying after ${backoff}s..." | |
| sleep "$backoff" | |
| done | |
| env: | |
| INGRESS_CLASS_NAME: ${{ env.INGRESS_CLASS_NAME }} | |
| AZURE_CLIENT_ID: ${{ env.WORKLOAD_AZURE_CLIENT_ID }} | |
| AZURE_TENANT_ID: ${{ env.AZURE_TENANT_ID }} | |
| PROJECT_ENDPOINT: ${{ needs.provision.outputs.PROJECT_ENDPOINT }} | |
| PROJECT_NAME: ${{ needs.provision.outputs.PROJECT_NAME }} | |
| MODEL_DEPLOYMENT_NAME_FAST: gpt-5-nano | |
| MODEL_DEPLOYMENT_NAME_RICH: gpt-5-nano | |
| COSMOS_ACCOUNT_URI: ${{ needs.provision.outputs.COSMOS_ACCOUNT_URI }} | |
| COSMOS_DATABASE: ${{ needs.provision.outputs.COSMOS_DATABASE }} | |
| REDIS_HOST: ${{ needs.provision.outputs.REDIS_HOST }} | |
| EVENT_HUB_NAMESPACE: ${{ needs.provision.outputs.EVENT_HUB_NAMESPACE }} | |
| KEY_VAULT_URI: ${{ needs.provision.outputs.KEY_VAULT_URI }} | |
| APPLICATIONINSIGHTS_CONNECTION_STRING: ${{ needs.provision.outputs.APPLICATIONINSIGHTS_CONNECTION_STRING }} | |
| POSTGRES_HOST: ${{ needs.provision.outputs.POSTGRES_HOST }} | |
| POSTGRES_USER: ${{ needs.provision.outputs.POSTGRES_USER }} | |
| POSTGRES_DATABASE: ${{ needs.provision.outputs.POSTGRES_DATABASE }} | |
| deploy-ui: | |
| runs-on: ubuntu-latest | |
| if: ${{ inputs.deployStatic && !inputs.uiOnly && needs.detect-changes.outputs.ui_changed == 'true' }} | |
| needs: | |
| - provision | |
| - detect-changes | |
| environment: ${{ inputs.environment }} | |
| env: | |
| AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Azure login (OIDC) | |
| uses: azure/login@v2 | |
| with: | |
| client-id: ${{ env.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ env.AZURE_TENANT_ID }} | |
| subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }} | |
| - name: Setup azd | |
| uses: Azure/setup-azd@v2 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Resolve API URL | |
| id: api | |
| run: | | |
| RESOURCE_GROUP="${{ inputs.projectName }}-${{ inputs.environment }}-rg" | |
| APIM_URL=$(az apim show \ | |
| --name "${{ inputs.projectName }}-${{ inputs.environment }}-apim" \ | |
| --resource-group "$RESOURCE_GROUP" \ | |
| --query "gatewayUrl" -o tsv) | |
| echo "api_url=$APIM_URL" >> "$GITHUB_OUTPUT" | |
| - name: Resolve SWA deployment token | |
| id: swa | |
| run: | | |
| RESOURCE_GROUP="${{ inputs.projectName }}-${{ inputs.environment }}-rg" | |
| SWA_NAME=$(az resource list \ | |
| --resource-group "$RESOURCE_GROUP" \ | |
| --resource-type "Microsoft.Web/staticSites" \ | |
| --query "[0].name" -o tsv) | |
| if [ -z "$SWA_NAME" ]; then | |
| echo "No Static Web App found in resource group $RESOURCE_GROUP" | |
| exit 1 | |
| fi | |
| SWA_TOKEN=$(az staticwebapp secrets list \ | |
| --name "$SWA_NAME" \ | |
| --resource-group "$RESOURCE_GROUP" \ | |
| --query "properties.apiKey" -o tsv) | |
| if [ -z "$SWA_TOKEN" ]; then | |
| echo "Failed to resolve deployment token for SWA $SWA_NAME" | |
| exit 1 | |
| fi | |
| echo "::add-mask::$SWA_TOKEN" | |
| echo "swa_name=$SWA_NAME" >> "$GITHUB_OUTPUT" | |
| echo "swa_token=$SWA_TOKEN" >> "$GITHUB_OUTPUT" | |
| - name: Deploy UI via Static Web Apps action | |
| uses: Azure/static-web-apps-deploy@v1 | |
| with: | |
| action: upload | |
| azure_static_web_apps_api_token: ${{ steps.swa.outputs.swa_token }} | |
| app_location: apps/ui | |
| output_location: '' | |
| skip_api_build: true | |
| app_build_command: yarn install --frozen-lockfile && yarn build | |
| env: | |
| NEXT_PUBLIC_API_URL: ${{ steps.api.outputs.api_url }} | |
| NEXT_PUBLIC_CRUD_API_URL: ${{ steps.api.outputs.api_url }} | |
| NEXT_PUBLIC_ENTRA_CLIENT_ID: ${{ secrets.NEXT_PUBLIC_ENTRA_CLIENT_ID }} | |
| NEXT_PUBLIC_ENTRA_TENANT_ID: ${{ secrets.NEXT_PUBLIC_ENTRA_TENANT_ID }} | |
| deploy-ui-token: | |
| runs-on: ubuntu-latest | |
| if: ${{ inputs.deployStatic && inputs.uiOnly }} | |
| environment: ${{ inputs.environment }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Resolve API URL | |
| id: api | |
| run: | | |
| if [ -n "${{ inputs.apiBaseUrl }}" ]; then | |
| echo "api_url=${{ inputs.apiBaseUrl }}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "api_url=https://apim-${{ inputs.projectName }}-${{ inputs.environment }}.azure-api.net" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Deploy UI via Static Web Apps action (token mode) | |
| uses: Azure/static-web-apps-deploy@v1 | |
| with: | |
| action: upload | |
| azure_static_web_apps_api_token: ${{ secrets.SWA_DEPLOYMENT_TOKEN }} | |
| app_location: apps/ui | |
| output_location: '' | |
| skip_api_build: true | |
| app_build_command: yarn install --frozen-lockfile && yarn build | |
| env: | |
| NEXT_PUBLIC_API_URL: ${{ steps.api.outputs.api_url }} | |
| NEXT_PUBLIC_CRUD_API_URL: ${{ steps.api.outputs.api_url }} | |
| NEXT_PUBLIC_ENTRA_CLIENT_ID: ${{ secrets.NEXT_PUBLIC_ENTRA_CLIENT_ID }} | |
| NEXT_PUBLIC_ENTRA_TENANT_ID: ${{ secrets.NEXT_PUBLIC_ENTRA_TENANT_ID }} | |
| deploy-agents: | |
| runs-on: ubuntu-latest | |
| if: ${{ !inputs.uiOnly && needs.detect-changes.outputs.agents_changed == 'true' }} | |
| needs: | |
| - provision | |
| - deploy-crud | |
| - deploy-foundry-models | |
| - detect-changes | |
| environment: ${{ inputs.environment }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJSON(needs.detect-changes.outputs.changed_agents_matrix) }} | |
| env: | |
| AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Azure login (OIDC) | |
| uses: azure/login@v2 | |
| with: | |
| client-id: ${{ env.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ env.AZURE_TENANT_ID }} | |
| subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }} | |
| - name: Setup azd | |
| uses: Azure/setup-azd@v2 | |
| - name: Authenticate azd (OIDC) | |
| run: | | |
| azd auth login \ | |
| --client-id "${AZURE_CLIENT_ID}" \ | |
| --tenant-id "${AZURE_TENANT_ID}" \ | |
| --federated-credential-provider github \ | |
| --no-prompt | |
| - name: Get AKS credentials | |
| run: | | |
| az aks get-credentials \ | |
| --resource-group "${{ inputs.projectName }}-${{ inputs.environment }}-rg" \ | |
| --name "${{ inputs.projectName }}-${{ inputs.environment }}-aks" \ | |
| --overwrite-existing | |
| - name: Resolve AKS managed identity client ID | |
| run: | | |
| AKS_MI_CLIENT_ID=$(az aks show \ | |
| --resource-group "${{ inputs.projectName }}-${{ inputs.environment }}-rg" \ | |
| --name "${{ inputs.projectName }}-${{ inputs.environment }}-aks" \ | |
| --query "identityProfile.kubeletidentity.clientId" -o tsv) | |
| echo "WORKLOAD_AZURE_CLIENT_ID=${AKS_MI_CLIENT_ID}" >> "$GITHUB_ENV" | |
| - name: Deploy service | |
| run: azd deploy --service "${{ matrix.service }}" --no-prompt -e "${{ inputs.environment }}" | |
| env: | |
| AZURE_CLIENT_ID: ${{ env.WORKLOAD_AZURE_CLIENT_ID }} | |
| AZURE_TENANT_ID: ${{ env.AZURE_TENANT_ID }} | |
| PROJECT_ENDPOINT: ${{ needs.provision.outputs.PROJECT_ENDPOINT }} | |
| PROJECT_NAME: ${{ needs.provision.outputs.PROJECT_NAME }} | |
| FOUNDRY_AUTO_ENSURE_ON_STARTUP: "true" | |
| FOUNDRY_STRICT_ENFORCEMENT: "true" | |
| MODEL_DEPLOYMENT_NAME_FAST: gpt-5-nano | |
| MODEL_DEPLOYMENT_NAME_RICH: gpt-5-nano | |
| COSMOS_ACCOUNT_URI: ${{ needs.provision.outputs.COSMOS_ACCOUNT_URI }} | |
| COSMOS_DATABASE: ${{ needs.provision.outputs.COSMOS_DATABASE }} | |
| REDIS_HOST: ${{ needs.provision.outputs.REDIS_HOST }} | |
| EVENT_HUB_NAMESPACE: ${{ needs.provision.outputs.EVENT_HUB_NAMESPACE }} | |
| KEY_VAULT_URI: ${{ needs.provision.outputs.KEY_VAULT_URI }} | |
| APPLICATIONINSIGHTS_CONNECTION_STRING: ${{ needs.provision.outputs.APPLICATIONINSIGHTS_CONNECTION_STRING }} | |
| POSTGRES_HOST: ${{ needs.provision.outputs.POSTGRES_HOST }} | |
| POSTGRES_USER: ${{ needs.provision.outputs.POSTGRES_USER }} | |
| POSTGRES_DATABASE: ${{ needs.provision.outputs.POSTGRES_DATABASE }} | |
| sync-apim: | |
| runs-on: ubuntu-latest | |
| if: ${{ !inputs.uiOnly && (needs.detect-changes.outputs.agents_changed == 'true' || needs.detect-changes.outputs.crud_changed == 'true') && (needs.deploy-crud.result == 'success' || needs.deploy-crud.result == 'skipped') && (needs.deploy-agents.result == 'success' || needs.deploy-agents.result == 'skipped') }} | |
| needs: | |
| - deploy-crud | |
| - deploy-agents | |
| - detect-changes | |
| environment: ${{ inputs.environment }} | |
| env: | |
| AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| CHANGED_SERVICES: ${{ needs.detect-changes.outputs.changed_aks_services_csv }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Azure login (OIDC) | |
| uses: azure/login@v2 | |
| with: | |
| client-id: ${{ env.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ env.AZURE_TENANT_ID }} | |
| subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }} | |
| - name: Get AKS credentials | |
| run: | | |
| az aks get-credentials \ | |
| --resource-group "${{ inputs.projectName }}-${{ inputs.environment }}-rg" \ | |
| --name "${{ inputs.projectName }}-${{ inputs.environment }}-aks" \ | |
| --overwrite-existing | |
| - name: Sync APIM agent APIs | |
| run: | | |
| bash .infra/azd/hooks/sync-apim-agents.sh --use-ingress \ | |
| --app-gw-name "${{ inputs.projectName }}-${{ inputs.environment }}-appgw" | |
| env: | |
| AZURE_RESOURCE_GROUP: ${{ inputs.projectName }}-${{ inputs.environment }}-rg | |
| ensure-foundry-agents: | |
| runs-on: ubuntu-latest | |
| if: ${{ !inputs.uiOnly && needs.detect-changes.outputs.agents_changed == 'true' && (needs.deploy-agents.result == 'success' || needs.deploy-agents.result == 'skipped') }} | |
| needs: | |
| - deploy-agents | |
| - detect-changes | |
| environment: ${{ inputs.environment }} | |
| env: | |
| AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| CHANGED_SERVICES: ${{ needs.detect-changes.outputs.changed_agent_services_csv }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Azure login (OIDC) | |
| uses: azure/login@v2 | |
| with: | |
| client-id: ${{ env.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ env.AZURE_TENANT_ID }} | |
| subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }} | |
| - name: Setup Python (for azure.yaml parser) | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.13' | |
| - name: Get AKS credentials | |
| run: | | |
| az aks get-credentials \ | |
| --resource-group "${{ inputs.projectName }}-${{ inputs.environment }}-rg" \ | |
| --name "${{ inputs.projectName }}-${{ inputs.environment }}-aks" \ | |
| --overwrite-existing | |
| - name: Ensure all V2 Foundry agents are provisioned | |
| run: | | |
| bash .infra/azd/hooks/ensure-foundry-agents.sh --port-forward | |
| env: | |
| K8S_NAMESPACE: holiday-peak | |
| - name: Verify Foundry readiness across services | |
| run: | | |
| if [ -n "${CHANGED_SERVICES}" ]; then | |
| SERVICES=$(printf '%s' "${CHANGED_SERVICES}" | tr ',' '\n' | sed '/^crud-service$/d' | sed '/^$/d') | |
| else | |
| SERVICES=$(python3 -c " | |
| import re | |
| with open('azure.yaml') as f: lines = f.readlines() | |
| s, cs, ch = False, None, None | |
| svcs = [] | |
| for l in lines: | |
| l = l.rstrip() | |
| if not s: | |
| if re.match(r'^services:\s*$', l): s = True | |
| continue | |
| if re.match(r'^[^\s]', l): break | |
| m = re.match(r'^ ([a-z0-9\-]+):\s*$', l) | |
| if m: | |
| if cs and ch == 'aks' and cs != 'crud-service': svcs.append(cs) | |
| cs, ch = m.group(1), None | |
| continue | |
| h = re.match(r'^ host:\s*(\S+)', l) | |
| if h: ch = h.group(1) | |
| if cs and ch == 'aks' and cs != 'crud-service': svcs.append(cs) | |
| print('\\n'.join(svcs)) | |
| ") | |
| fi | |
| FAILED=0 | |
| echo "$SERVICES" | while IFS= read -r SVC; do | |
| [ -z "$SVC" ] && continue | |
| LOCAL_PORT=$(python3 -c 'import socket; s=socket.socket(); s.bind(("",0)); print(s.getsockname()[1]); s.close()') | |
| kubectl port-forward "svc/$SVC" "$LOCAL_PORT:8000" -n holiday-peak & | |
| PF_PID=$! | |
| sleep 3 | |
| STATUS=$(curl -s "http://localhost:$LOCAL_PORT/health" 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin).get('status','unknown'))" 2>/dev/null || echo "unreachable") | |
| kill "$PF_PID" 2>/dev/null || true | |
| if [ "$STATUS" = "ok" ]; then | |
| echo " [OK] $SVC" | |
| else | |
| echo " [WARN] $SVC: status=$STATUS" | |
| FAILED=$((FAILED + 1)) | |
| fi | |
| done | |
| if [ "$FAILED" -gt 0 ]; then | |
| echo "WARNING: $FAILED service(s) not fully ready." | |
| fi | |
| seed-demo-data: | |
| runs-on: ubuntu-latest | |
| needs: | |
| - sync-apim | |
| - ensure-foundry-agents | |
| - deploy-ui | |
| if: ${{ always() && inputs.seedDemoData && needs.ensure-foundry-agents.result == 'success' && (needs.deploy-ui.result == 'success' || needs.deploy-ui.result == 'skipped') && inputs.environment != 'prod' && inputs.environment != 'production' }} | |
| environment: ${{ inputs.environment }} | |
| env: | |
| AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Azure login (OIDC) | |
| uses: azure/login@v2 | |
| with: | |
| client-id: ${{ env.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ env.AZURE_TENANT_ID }} | |
| subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }} | |
| - name: Setup azd | |
| uses: Azure/setup-azd@v2 | |
| - name: Resolve environment values | |
| run: | | |
| eval "$(azd env get-values -e '${{ inputs.environment }}' | sed 's/^/export /')" | |
| if [ -z "${SERVICE_CRUD_SERVICE_IMAGE_NAME:-}" ]; then | |
| echo "SERVICE_CRUD_SERVICE_IMAGE_NAME is required for seed job" | |
| exit 1 | |
| fi | |
| echo "CRUD_IMAGE=${SERVICE_CRUD_SERVICE_IMAGE_NAME}" >> "$GITHUB_ENV" | |
| echo "POSTGRES_HOST=${POSTGRES_HOST}" >> "$GITHUB_ENV" | |
| echo "POSTGRES_USER=${POSTGRES_USER}" >> "$GITHUB_ENV" | |
| echo "POSTGRES_PASSWORD=${POSTGRES_PASSWORD}" >> "$GITHUB_ENV" | |
| echo "POSTGRES_DATABASE=${POSTGRES_DATABASE}" >> "$GITHUB_ENV" | |
| echo "POSTGRES_PORT=${POSTGRES_PORT}" >> "$GITHUB_ENV" | |
| echo "POSTGRES_SSL=${POSTGRES_SSL}" >> "$GITHUB_ENV" | |
| - name: Get AKS credentials | |
| run: | | |
| az aks get-credentials \ | |
| --resource-group "${{ inputs.projectName }}-${{ inputs.environment }}-rg" \ | |
| --name "${{ inputs.projectName }}-${{ inputs.environment }}-aks" \ | |
| --overwrite-existing | |
| - name: Run demo faker seed job | |
| run: | | |
| JOB_NAME="crud-demo-seed-${{ github.run_id }}" | |
| cat <<EOF | kubectl apply -f - | |
| apiVersion: batch/v1 | |
| kind: Job | |
| metadata: | |
| name: ${JOB_NAME} | |
| namespace: holiday-peak | |
| spec: | |
| backoffLimit: 1 | |
| ttlSecondsAfterFinished: 600 | |
| template: | |
| spec: | |
| restartPolicy: Never | |
| containers: | |
| - name: seed | |
| image: ${CRUD_IMAGE} | |
| imagePullPolicy: Always | |
| command: ["python", "-m", "crud_service.scripts.seed_demo_data"] | |
| env: | |
| - name: DEMO_ENVIRONMENT | |
| value: "${{ inputs.environment }}" | |
| - name: POSTGRES_HOST | |
| value: "${POSTGRES_HOST}" | |
| - name: POSTGRES_USER | |
| value: "${POSTGRES_USER}" | |
| - name: POSTGRES_PASSWORD | |
| value: "${POSTGRES_PASSWORD}" | |
| - name: POSTGRES_DATABASE | |
| value: "${POSTGRES_DATABASE}" | |
| - name: POSTGRES_PORT | |
| value: "${POSTGRES_PORT}" | |
| - name: POSTGRES_SSL | |
| value: "${POSTGRES_SSL}" | |
| EOF | |
| kubectl wait --for=condition=complete "job/${JOB_NAME}" -n holiday-peak --timeout=10m | |
| kubectl logs "job/${JOB_NAME}" -n holiday-peak |