diff --git a/.github/workflows/deploy-orchestrator.yml b/.github/workflows/deploy-orchestrator.yml index 81d2db410..1ffe3adcb 100644 --- a/.github/workflows/deploy-orchestrator.yml +++ b/.github/workflows/deploy-orchestrator.yml @@ -4,7 +4,7 @@ on: workflow_call: inputs: runner_os: - description: 'Runner OS (ubuntu-latest, windows-latest, or devcontainer)' + description: 'Runner OS (ubuntu-latest or windows-latest)' required: true type: string azure_location: @@ -69,11 +69,7 @@ on: env: AZURE_DEV_COLLECT_TELEMETRY: ${{ vars.AZURE_DEV_COLLECT_TELEMETRY }} -permissions: - contents: read - actions: read - packages: write # Required by job-deploy → job-deploy-devcontainer to push devcontainer image to GHCR - + jobs: docker-build: uses: ./.github/workflows/job-docker-build.yml diff --git a/.github/workflows/deploy-ci.yml b/.github/workflows/deploy-v2.yml similarity index 97% rename from .github/workflows/deploy-ci.yml rename to .github/workflows/deploy-v2.yml index 75453fdb2..4730ee4d9 100644 --- a/.github/workflows/deploy-ci.yml +++ b/.github/workflows/deploy-v2.yml @@ -27,7 +27,6 @@ on: type: choice options: - 'codespace' - - 'Devcontainer' - 'Local' default: 'codespace' @@ -116,7 +115,8 @@ on: permissions: contents: read actions: read - packages: write # Required by deploy-orchestrator → job-deploy → job-deploy-devcontainer for GHCR + id-token: write # Required for OIDC-based Azure authentication + packages: write # Required for GHCR operations jobs: validate-inputs: runs-on: ubuntu-latest @@ -157,8 +157,8 @@ jobs: # Validate runner_os (specific allowed values) and derive actual runner RUNNER_OS_INPUT="${INPUT_RUNNER_OS:-codespace}" - if [[ "$RUNNER_OS_INPUT" != "codespace" && "$RUNNER_OS_INPUT" != "Devcontainer" && "$RUNNER_OS_INPUT" != "Local" ]]; then - echo "❌ ERROR: runner_os must be one of: codespace, Devcontainer, Local, got: '$RUNNER_OS_INPUT'" + if [[ "$RUNNER_OS_INPUT" != "codespace" && "$RUNNER_OS_INPUT" != "Local" ]]; then + echo "❌ ERROR: runner_os must be one of: codespace, Local, got: '$RUNNER_OS_INPUT'" VALIDATION_FAILED=true else echo "✅ runner_os: '$RUNNER_OS_INPUT' is valid" @@ -167,8 +167,6 @@ jobs: # Derive actual runner from runner_os input if [[ "$RUNNER_OS_INPUT" == "codespace" ]]; then RUNNER_OS="ubuntu-latest" - elif [[ "$RUNNER_OS_INPUT" == "Devcontainer" ]]; then - RUNNER_OS="devcontainer" else RUNNER_OS="windows-latest" fi diff --git a/.github/workflows/deploy-windows.yml b/.github/workflows/deploy-windows.yml deleted file mode 100644 index b2fe3dc3a..000000000 --- a/.github/workflows/deploy-windows.yml +++ /dev/null @@ -1,275 +0,0 @@ -name: Deploy-Test-Cleanup (v2) Windows -on: - # pull_request: - # branches: - # - main - # workflow_run: - # workflows: ["Build Docker and Optional Push"] - # types: - # - completed - # branches: - # - main - # - dev - # - demo - workflow_dispatch: - inputs: - azure_location: - description: 'Azure Location For Deployment' - required: false - default: 'australiaeast' - type: choice - options: - - 'australiaeast' - - 'centralus' - - 'eastasia' - - 'eastus2' - - 'japaneast' - - 'northeurope' - - 'southeastasia' - - 'uksouth' - resource_group_name: - description: 'Resource Group Name (Optional)' - required: false - default: '' - type: string - - waf_enabled: - description: 'Enable WAF' - required: false - default: false - type: boolean - EXP: - description: 'Enable EXP' - required: false - default: false - type: boolean - build_docker_image: - description: 'Build And Push Docker Image (Optional)' - required: false - default: false - type: boolean - - cleanup_resources: - description: 'Cleanup Deployed Resources' - required: false - default: false - type: boolean - - run_e2e_tests: - description: 'Run End-to-End Tests' - required: false - default: 'GoldenPath-Testing' - type: choice - options: - - 'GoldenPath-Testing' - - 'Smoke-Testing' - - 'None' - - AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: - description: 'Log Analytics Workspace ID (Optional)' - required: false - default: '' - type: string - AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: - description: 'AI Project Resource ID (Optional)' - required: false - default: '' - type: string - existing_webapp_url: - description: 'Existing WebApp URL (Skips Deployment)' - required: false - default: '' - type: string - - # schedule: - # - cron: '0 9,21 * * *' # Runs at 9:00 AM and 9:00 PM GMT -permissions: - contents: read - actions: read - packages: write # Required by deploy-orchestrator → job-deploy → job-deploy-devcontainer for GHCR - -jobs: - validate-inputs: - runs-on: ubuntu-latest - outputs: - validation_passed: ${{ steps.validate.outputs.passed }} - azure_location: ${{ steps.validate.outputs.azure_location }} - resource_group_name: ${{ steps.validate.outputs.resource_group_name }} - waf_enabled: ${{ steps.validate.outputs.waf_enabled }} - exp: ${{ steps.validate.outputs.exp }} - build_docker_image: ${{ steps.validate.outputs.build_docker_image }} - cleanup_resources: ${{ steps.validate.outputs.cleanup_resources }} - run_e2e_tests: ${{ steps.validate.outputs.run_e2e_tests }} - azure_env_log_analytics_workspace_id: ${{ steps.validate.outputs.azure_env_log_analytics_workspace_id }} - azure_existing_ai_project_resource_id: ${{ steps.validate.outputs.azure_existing_ai_project_resource_id }} - existing_webapp_url: ${{ steps.validate.outputs.existing_webapp_url }} - steps: - - name: Validate Workflow Input Parameters - id: validate - shell: bash - env: - INPUT_AZURE_LOCATION: ${{ github.event.inputs.azure_location }} - INPUT_RESOURCE_GROUP_NAME: ${{ github.event.inputs.resource_group_name }} - INPUT_WAF_ENABLED: ${{ github.event.inputs.waf_enabled }} - INPUT_EXP: ${{ github.event.inputs.EXP }} - INPUT_BUILD_DOCKER_IMAGE: ${{ github.event.inputs.build_docker_image }} - INPUT_CLEANUP_RESOURCES: ${{ github.event.inputs.cleanup_resources }} - INPUT_RUN_E2E_TESTS: ${{ github.event.inputs.run_e2e_tests }} - INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ github.event.inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }} - INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ github.event.inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }} - INPUT_EXISTING_WEBAPP_URL: ${{ github.event.inputs.existing_webapp_url }} - run: | - echo "🔍 Validating workflow input parameters..." - VALIDATION_FAILED=false - - # Validate azure_location (Azure region format) - LOCATION="${INPUT_AZURE_LOCATION:-australiaeast}" - - if [[ ! "$LOCATION" =~ ^[a-z0-9]+$ ]]; then - echo "❌ ERROR: azure_location '$LOCATION' is invalid. Must contain only lowercase letters and numbers" - VALIDATION_FAILED=true - else - echo "✅ azure_location: '$LOCATION' is valid" - fi - - # Validate resource_group_name (Azure naming convention, optional) - if [[ -n "$INPUT_RESOURCE_GROUP_NAME" ]]; then - if [[ ! "$INPUT_RESOURCE_GROUP_NAME" =~ ^[a-zA-Z0-9._\(\)-]+$ ]] || [[ "$INPUT_RESOURCE_GROUP_NAME" =~ \.$ ]]; then - echo "❌ ERROR: resource_group_name '$INPUT_RESOURCE_GROUP_NAME' is invalid. Must contain only alphanumerics, periods, underscores, hyphens, and parentheses. Cannot end with period." - VALIDATION_FAILED=true - elif [[ ${#INPUT_RESOURCE_GROUP_NAME} -gt 90 ]]; then - echo "❌ ERROR: resource_group_name '$INPUT_RESOURCE_GROUP_NAME' exceeds 90 characters (length: ${#INPUT_RESOURCE_GROUP_NAME})" - VALIDATION_FAILED=true - else - echo "✅ resource_group_name: '$INPUT_RESOURCE_GROUP_NAME' is valid" - fi - else - echo "✅ resource_group_name: Not provided (will be auto-generated)" - fi - - # Validate waf_enabled (boolean) - WAF_ENABLED="${INPUT_WAF_ENABLED:-false}" - if [[ "$WAF_ENABLED" != "true" && "$WAF_ENABLED" != "false" ]]; then - echo "❌ ERROR: waf_enabled must be 'true' or 'false', got: '$WAF_ENABLED'" - VALIDATION_FAILED=true - else - echo "✅ waf_enabled: '$WAF_ENABLED' is valid" - fi - - # Validate EXP (boolean) - EXP_ENABLED="${INPUT_EXP:-false}" - if [[ "$EXP_ENABLED" != "true" && "$EXP_ENABLED" != "false" ]]; then - echo "❌ ERROR: EXP must be 'true' or 'false', got: '$EXP_ENABLED'" - VALIDATION_FAILED=true - else - echo "✅ EXP: '$EXP_ENABLED' is valid" - fi - - # Validate build_docker_image (boolean) - BUILD_DOCKER="${INPUT_BUILD_DOCKER_IMAGE:-false}" - if [[ "$BUILD_DOCKER" != "true" && "$BUILD_DOCKER" != "false" ]]; then - echo "❌ ERROR: build_docker_image must be 'true' or 'false', got: '$BUILD_DOCKER'" - VALIDATION_FAILED=true - else - echo "✅ build_docker_image: '$BUILD_DOCKER' is valid" - fi - - # Validate cleanup_resources (boolean) - CLEANUP_RESOURCES="${INPUT_CLEANUP_RESOURCES:-false}" - if [[ "$CLEANUP_RESOURCES" != "true" && "$CLEANUP_RESOURCES" != "false" ]]; then - echo "❌ ERROR: cleanup_resources must be 'true' or 'false', got: '$CLEANUP_RESOURCES'" - VALIDATION_FAILED=true - else - echo "✅ cleanup_resources: '$CLEANUP_RESOURCES' is valid" - fi - - # Validate run_e2e_tests (specific allowed values) - TEST_OPTION="${INPUT_RUN_E2E_TESTS:-GoldenPath-Testing}" - if [[ "$TEST_OPTION" != "GoldenPath-Testing" && "$TEST_OPTION" != "Smoke-Testing" && "$TEST_OPTION" != "None" ]]; then - echo "❌ ERROR: run_e2e_tests must be one of: GoldenPath-Testing, Smoke-Testing, None, got: '$TEST_OPTION'" - VALIDATION_FAILED=true - else - echo "✅ run_e2e_tests: '$TEST_OPTION' is valid" - fi - - # Validate AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID (optional, Azure Resource ID format) - if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then - if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/[Rr]esource[Gg]roups/[^/]+/providers/[Mm]icrosoft\.[Oo]perational[Ii]nsights/[Ww]orkspaces/[^/]+$ ]]; then - echo "❌ ERROR: AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID is invalid. Must be a valid Azure Resource ID format:" - echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}" - echo " Got: '$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID'" - VALIDATION_FAILED=true - else - echo "✅ AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: Valid Resource ID format" - fi - else - echo "✅ AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: Not provided (optional)" - fi - - # Validate AZURE_EXISTING_AI_PROJECT_RESOURCE_ID (optional, Azure Resource ID format) - if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then - if [[ ! "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/[Rr]esource[Gg]roups/[^/]+/providers/([Mm]icrosoft\.[Mm]achine[Ll]earning[Ss]ervices/([Ww]orkspaces|[Pp]rojects)/[^/]+|[Mm]icrosoft\.[Cc]ognitive[Ss]ervices/[Aa]ccounts/[^/]+/[Pp]rojects/[^/]+)$ ]]; then - echo "❌ ERROR: AZURE_EXISTING_AI_PROJECT_RESOURCE_ID is invalid. Must be a valid Azure Resource ID format:" - echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CognitiveServices/accounts/{accountName}/projects/{projectName}" - echo " Got: '$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID'" - VALIDATION_FAILED=true - else - echo "✅ AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: Valid Resource ID format" - fi - else - echo "✅ AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: Not provided (optional)" - fi - - # Validate existing_webapp_url (optional, must start with https) - if [[ -n "$INPUT_EXISTING_WEBAPP_URL" ]]; then - if [[ ! "$INPUT_EXISTING_WEBAPP_URL" =~ ^https:// ]]; then - echo "❌ ERROR: existing_webapp_url must start with 'https://', got: '$INPUT_EXISTING_WEBAPP_URL'" - VALIDATION_FAILED=true - else - echo "✅ existing_webapp_url: '$INPUT_EXISTING_WEBAPP_URL' is valid" - fi - else - echo "✅ existing_webapp_url: Not provided (will perform deployment)" - fi - - # Fail workflow if any validation failed - if [[ "$VALIDATION_FAILED" == "true" ]]; then - echo "" - echo "❌ Parameter validation failed. Please correct the errors above and try again." - exit 1 - fi - - echo "" - echo "✅ All input parameters validated successfully!" - - # Output validated values - echo "passed=true" >> $GITHUB_OUTPUT - echo "azure_location=$LOCATION" >> $GITHUB_OUTPUT - echo "resource_group_name=$INPUT_RESOURCE_GROUP_NAME" >> $GITHUB_OUTPUT - echo "waf_enabled=$WAF_ENABLED" >> $GITHUB_OUTPUT - echo "exp=$EXP_ENABLED" >> $GITHUB_OUTPUT - echo "build_docker_image=$BUILD_DOCKER" >> $GITHUB_OUTPUT - echo "cleanup_resources=$CLEANUP_RESOURCES" >> $GITHUB_OUTPUT - echo "run_e2e_tests=$TEST_OPTION" >> $GITHUB_OUTPUT - echo "azure_env_log_analytics_workspace_id=$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" >> $GITHUB_OUTPUT - echo "azure_existing_ai_project_resource_id=$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" >> $GITHUB_OUTPUT - echo "existing_webapp_url=$INPUT_EXISTING_WEBAPP_URL" >> $GITHUB_OUTPUT - - Run: - needs: validate-inputs - if: needs.validate-inputs.outputs.validation_passed == 'true' - uses: ./.github/workflows/deploy-orchestrator.yml - with: - runner_os: windows-latest - azure_location: ${{ needs.validate-inputs.outputs.azure_location || 'australiaeast' }} - resource_group_name: ${{ needs.validate-inputs.outputs.resource_group_name || '' }} - waf_enabled: ${{ needs.validate-inputs.outputs.waf_enabled == 'true' }} - EXP: ${{ needs.validate-inputs.outputs.exp == 'true' }} - build_docker_image: ${{ needs.validate-inputs.outputs.build_docker_image == 'true' }} - cleanup_resources: ${{ needs.validate-inputs.outputs.cleanup_resources == 'true' }} - run_e2e_tests: ${{ needs.validate-inputs.outputs.run_e2e_tests || 'GoldenPath-Testing' }} - AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ needs.validate-inputs.outputs.azure_env_log_analytics_workspace_id || '' }} - AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ needs.validate-inputs.outputs.azure_existing_ai_project_resource_id || '' }} - existing_webapp_url: ${{ needs.validate-inputs.outputs.existing_webapp_url || '' }} - trigger_type: ${{ github.event_name }} - secrets: inherit diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index eecdd2798..ee0bc9a25 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -31,9 +31,11 @@ on: permissions: contents: read actions: read + id-token: write # Required for OIDC-based Azure authentication jobs: build-and-push: runs-on: ubuntu-latest + environment: production steps: - name: Checkout repository @@ -42,13 +44,18 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Log in to Azure Container Registry + - name: Login to Azure (OIDC) if: ${{ (github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'dev' || github.ref_name == 'demo')) || (github.event_name == 'workflow_dispatch' && (github.ref_name == 'dependabotchanges'||github.ref_name == 'main' || github.ref_name == 'dev' || github.ref_name == 'demo')) }} - uses: azure/docker-login@v2 + uses: azure/login@v2 with: - login-server: ${{ secrets.ACR_LOGIN_SERVER }} - username: ${{ secrets.ACR_USERNAME }} - password: ${{ secrets.ACR_PASSWORD }} + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Log in to Azure Container Registry + if: ${{ (github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'dev' || github.ref_name == 'demo')) || (github.event_name == 'workflow_dispatch' && (github.ref_name == 'dependabotchanges'||github.ref_name == 'main' || github.ref_name == 'dev' || github.ref_name == 'demo')) }} + shell: bash + run: az acr login --name ${{ secrets.ACR_LOGIN_SERVER }} - name: Get current date id: date diff --git a/.github/workflows/job-cleanup-deployment.yml b/.github/workflows/job-cleanup-deployment.yml index 0e8aef426..c06039378 100644 --- a/.github/workflows/job-cleanup-deployment.yml +++ b/.github/workflows/job-cleanup-deployment.yml @@ -40,13 +40,11 @@ on: description: 'Docker Image Tag' required: true type: string -permissions: - contents: read - actions: read - + jobs: cleanup-deployment: runs-on: ${{ inputs.runner_os }} + environment: production continue-on-error: true env: RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }} @@ -55,11 +53,12 @@ jobs: ENV_NAME: ${{ inputs.ENV_NAME }} IMAGE_TAG: ${{ inputs.IMAGE_TAG }} steps: - - name: Login to Azure - shell: bash - run: | - az login --service-principal -u ${{ secrets.AZURE_CLIENT_ID }} -p ${{ secrets.AZURE_CLIENT_SECRET }} --tenant ${{ secrets.AZURE_TENANT_ID }} - az account set --subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }} + - name: Login to Azure (OIDC) + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - name: Delete Resource Group (Optimized Cleanup) id: delete_rg diff --git a/.github/workflows/job-deploy-devcontainer.yml b/.github/workflows/job-deploy-devcontainer.yml deleted file mode 100644 index 4a864f382..000000000 --- a/.github/workflows/job-deploy-devcontainer.yml +++ /dev/null @@ -1,436 +0,0 @@ -name: Deploy Steps - DevContainer - -# The devcontainer (Dockerfile + features + post-create script) installs everything: -# Python 3.11, Azure CLI + Bicep, azd, Node.js, Poetry, pip dependencies -# So the workflow simply: builds the container → logs in → runs azd up. - -on: - workflow_call: - inputs: - ENV_NAME: - required: true - type: string - AZURE_ENV_OPENAI_LOCATION: - required: true - type: string - AZURE_LOCATION: - required: true - type: string - RESOURCE_GROUP_NAME: - required: true - type: string - IMAGE_TAG: - required: true - type: string - BUILD_DOCKER_IMAGE: - required: true - type: string - EXP: - required: true - type: string - WAF_ENABLED: - required: false - type: string - default: 'false' - AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: - required: false - type: string - AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: - required: false - type: string - outputs: - WEB_APPURL: - description: "Container Web App URL" - value: ${{ jobs.deploy-devcontainer.outputs.WEB_APPURL }} - -permissions: - contents: read - packages: write # Required to push devcontainer image to GHCR (optional caching) - -jobs: - deploy-devcontainer: - runs-on: ubuntu-latest - env: - AZURE_DEV_COLLECT_TELEMETRY: ${{ vars.AZURE_DEV_COLLECT_TELEMETRY }} - outputs: - WEB_APPURL: ${{ steps.load_azd_outputs.outputs.WEB_APP_URL }} - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - 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_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, underscores, 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, contain only alphanumerics, underscores, periods, hyphens, max 128 characters" - VALIDATION_FAILED=true - else - echo "✅ IMAGE_TAG: '$INPUT_IMAGE_TAG' is valid" - fi - - # Validate EXP (required, boolean string) - 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 (boolean string) - 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, Azure Resource ID format) - if [[ -n "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" ]]; then - if [[ ! "$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/[Rr]esource[Gg]roups/[^/]+/providers/[Mm]icrosoft\.[Oo]perational[Ii]nsights/[Ww]orkspaces/[^/]+$ ]]; then - echo "❌ ERROR: AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID is invalid. Must be a valid Azure Resource ID format:" - echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}" - echo " Got: '$INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID'" - VALIDATION_FAILED=true - else - echo "✅ AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: Valid Resource ID format" - fi - else - echo "✅ AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: Not provided (optional)" - fi - - # Validate AZURE_EXISTING_AI_PROJECT_RESOURCE_ID (optional, if provided must be valid Resource ID) - if [[ -n "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" ]]; then - if [[ ! "$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" =~ ^/subscriptions/[a-fA-F0-9-]+/[Rr]esource[Gg]roups/[^/]+/providers/([Mm]icrosoft\.[Mm]achine[Ll]earning[Ss]ervices/([Ww]orkspaces|[Pp]rojects)/[^/]+|[Mm]icrosoft\.[Cc]ognitive[Ss]ervices/[Aa]ccounts/[^/]+/[Pp]rojects/[^/]+)$ ]]; then - echo "❌ ERROR: AZURE_EXISTING_AI_PROJECT_RESOURCE_ID is invalid. Must be a valid Azure Resource ID format:" - echo " /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CognitiveServices/accounts/{accountName}/projects/{projectName}" - echo " Got: '$INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID'" - VALIDATION_FAILED=true - else - echo "✅ AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: Valid Resource ID format" - fi - else - echo "✅ AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: Not provided (optional)" - 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: Configure Parameters Based on WAF Setting - shell: bash - env: - WAF_ENABLED: ${{ inputs.WAF_ENABLED }} - run: | - if [[ "$WAF_ENABLED" == "true" ]]; then - cp content-gen/infra/main.waf.parameters.json content-gen/infra/main.parameters.json - echo "✅ Successfully copied WAF parameters to main parameters file" - else - echo "🔧 Configuring Non-WAF deployment - using default main.parameters.json..." - fi - - - name: Login to GHCR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set Lowercase Image Name - id: image_name - shell: bash - run: echo "IMAGE_NAME=ghcr.io/$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')/devcontainer" >> $GITHUB_ENV - - # Build devcontainer and run azd up inside it - - name: Deploy with azd up in DevContainer - uses: devcontainers/ci@v0.3 - env: - AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} - AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} - AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} - AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - 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_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 }} - SECRET_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ secrets.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }} - SECRET_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ secrets.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }} - SECRET_ACR_TEST_USERNAME: ${{ secrets.ACR_TEST_USERNAME }} - with: - # Cache the built devcontainer image to GHCR to reuse across runs - imageName: ${{ env.IMAGE_NAME }} - cacheFrom: ${{ env.IMAGE_NAME }} - push: always - - # Explicitly forward env vars into the devcontainer - env: | - AZURE_CLIENT_ID - AZURE_CLIENT_SECRET - AZURE_TENANT_ID - AZURE_SUBSCRIPTION_ID - INPUT_ENV_NAME - INPUT_AZURE_ENV_OPENAI_LOCATION - INPUT_AZURE_LOCATION - INPUT_RESOURCE_GROUP_NAME - INPUT_IMAGE_TAG - INPUT_BUILD_DOCKER_IMAGE - INPUT_EXP - INPUT_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID - INPUT_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID - SECRET_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID - SECRET_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID - SECRET_ACR_TEST_USERNAME - - runCmd: | - echo "🐳 Running inside devcontainer — same environment as Codespaces" - echo "Python: $(python3 --version)" - echo "Azure CLI: $(az version --query '"azure-cli"' -o tsv)" - echo "azd: $(azd version)" - echo "" - - set -e - - # 1. Authenticate Azure CLI and azd using client secret - echo "🔐 Logging in with Azure CLI (client secret)..." - az login --service-principal -u "$AZURE_CLIENT_ID" -p "$AZURE_CLIENT_SECRET" --tenant "$AZURE_TENANT_ID" - az account set --subscription "$AZURE_SUBSCRIPTION_ID" - - echo "🔐 Logging in with azd (client secret)..." - azd auth login \ - --client-id "$AZURE_CLIENT_ID" \ - --client-secret "$AZURE_CLIENT_SECRET" \ - --tenant-id "$AZURE_TENANT_ID" - - # 2. Change to content-gen directory where azure.yaml lives - cd content-gen - - # Initialize azd environment - echo "⚙️ Creating azd environment: $INPUT_ENV_NAME" - azd env new "$INPUT_ENV_NAME" --no-prompt - - echo "Setting default subscription..." - azd config set defaults.subscription "$AZURE_SUBSCRIPTION_ID" - - # Set core parameters - azd env set AZURE_SUBSCRIPTION_ID="$AZURE_SUBSCRIPTION_ID" - azd env set AZURE_ENV_OPENAI_LOCATION="$INPUT_AZURE_ENV_OPENAI_LOCATION" - azd env set AZURE_LOCATION="$INPUT_AZURE_LOCATION" - azd env set AZURE_RESOURCE_GROUP="$INPUT_RESOURCE_GROUP_NAME" - - # Set ACR endpoint based on BUILD_DOCKER_IMAGE flag - if [[ "$INPUT_BUILD_DOCKER_IMAGE" == "true" ]]; then - ACR_NAME=$(echo "$SECRET_ACR_TEST_USERNAME") - azd env set ACR_NAME="$ACR_NAME" - echo "Set ACR name to: $ACR_NAME" - else - echo "Skipping ACR name configuration (using existing image)" - fi - - # Set EXP parameters if enabled - if [[ "$INPUT_EXP" == "true" ]]; then - echo "✅ EXP ENABLED - Setting EXP parameters..." - - 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="$SECRET_AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID" - fi - - 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="$SECRET_AZURE_EXISTING_AI_PROJECT_RESOURCE_ID" - fi - - echo "AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: $EXP_LOG_ANALYTICS_ID" - echo "AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: $EXP_AI_PROJECT_ID" - azd env set AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID="$EXP_LOG_ANALYTICS_ID" - azd env set AZURE_EXISTING_AI_PROJECT_RESOURCE_ID="$EXP_AI_PROJECT_ID" - else - echo "❌ EXP DISABLED - Skipping EXP parameters" - 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 - - # 3. Deploy using azd up - echo "🚀 Running azd up..." - azd up --no-prompt - - # 4. Export azd env values to a file so the host can read them - echo "📝 Exporting azd environment values..." - azd env get-values > /tmp/azd-env-values.txt 2>/dev/null || true - cp /tmp/azd-env-values.txt "$PWD/.azd-env-output" || true - - # Also copy to the workspace root for the host step - cp /tmp/azd-env-values.txt "$GITHUB_WORKSPACE/.azd-env-output" || true - - echo "" - echo "✅ Deployment complete!" - - - name: Load azd Environment Outputs - id: load_azd_outputs - if: always() - shell: bash - run: | - # Read the azd env values exported from the devcontainer - AZD_OUTPUT_FILE="" - if [ -f .azd-env-output ]; then - AZD_OUTPUT_FILE=".azd-env-output" - elif [ -f content-gen/.azd-env-output ]; then - AZD_OUTPUT_FILE="content-gen/.azd-env-output" - fi - - if [ -n "$AZD_OUTPUT_FILE" ]; then - echo "📝 Loading azd environment values from $AZD_OUTPUT_FILE..." - - # Parse azd env output (format: KEY="value") - while IFS= read -r line; do - key=$(echo "$line" | cut -d'=' -f1) - value=$(echo "$line" | cut -d'=' -f2- | sed 's/^"//' | sed 's/"$//') - if [ -n "$key" ] && [ -n "$value" ]; then - echo "${key}=${value}" >> $GITHUB_ENV - fi - done < "$AZD_OUTPUT_FILE" - - echo "✅ Loaded azd environment values" - - # Extract Web App URL for downstream jobs - WEB_APP_URL=$(grep '^WEB_APP_URL=' "$AZD_OUTPUT_FILE" | cut -d'=' -f2- | sed 's/^"//' | sed 's/"$//') - echo "WEB_APP_URL=$WEB_APP_URL" >> $GITHUB_OUTPUT - else - echo "⚠️ No azd env output file found (.azd-env-output)" - fi - - - 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_APP_URL: ${{ steps.load_azd_outputs.outputs.WEB_APP_URL }} - run: | - echo "## 🐳 Deploy Job Summary (DevContainer)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY - echo "|-------|--------|" >> $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 - echo "### ✅ Deployment Details" >> $GITHUB_STEP_SUMMARY - echo "- **Web App URL**: [$WEB_APP_URL]($WEB_APP_URL)" >> $GITHUB_STEP_SUMMARY - echo "- Successfully deployed to Azure using devcontainer with all resources configured" >> $GITHUB_STEP_SUMMARY - else - echo "### ❌ Deployment Failed" >> $GITHUB_STEP_SUMMARY - echo "- Deployment process encountered an error" >> $GITHUB_STEP_SUMMARY - echo "- Check the deploy job for detailed error information" >> $GITHUB_STEP_SUMMARY - fi - - - name: Logout from Azure - if: always() - shell: bash - run: | - az logout || true - echo "Logged out from Azure." diff --git a/.github/workflows/job-deploy-linux.yml b/.github/workflows/job-deploy-linux.yml index e4338572a..5e4588d8a 100644 --- a/.github/workflows/job-deploy-linux.yml +++ b/.github/workflows/job-deploy-linux.yml @@ -38,13 +38,11 @@ on: WEB_APPURL: description: "Container Web App URL" value: ${{ jobs.deploy-linux.outputs.WEB_APP_URL }} -permissions: - contents: read - actions: read - + jobs: deploy-linux: runs-on: ubuntu-latest + environment: production env: AZURE_DEV_COLLECT_TELEMETRY: ${{ vars.AZURE_DEV_COLLECT_TELEMETRY }} outputs: @@ -200,13 +198,17 @@ jobs: - name: Install azd uses: Azure/setup-azd@v2 - - name: Login to AZD - id: login-azure + - name: Login to Azure (OIDC) + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Login to AZD (OIDC) shell: bash run: | - az login --service-principal -u ${{ secrets.AZURE_CLIENT_ID }} -p ${{ secrets.AZURE_CLIENT_SECRET }} --tenant ${{ secrets.AZURE_TENANT_ID }} - az account set --subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }} - azd auth login --client-id ${{ secrets.AZURE_CLIENT_ID }} --client-secret ${{ secrets.AZURE_CLIENT_SECRET }} --tenant-id ${{ secrets.AZURE_TENANT_ID }} + azd auth login --client-id ${{ secrets.AZURE_CLIENT_ID }} --federated-credential-provider github --tenant-id ${{ secrets.AZURE_TENANT_ID }} - name: Deploy using azd up and extract values (Linux) id: get_output_linux @@ -244,7 +246,7 @@ jobs: # Set ACR name only when building Docker image if [[ "$BUILD_DOCKER_IMAGE" == "true" ]]; then # Extract ACR name from login server and set as environment variable - ACR_NAME="${{ secrets.ACR_TEST_USERNAME }}" + ACR_NAME=$(echo "${{ secrets.ACR_TEST_LOGIN_SERVER }}" | cut -d'.' -f1) azd env set ACR_NAME="$ACR_NAME" echo "Set ACR name to: $ACR_NAME" else diff --git a/.github/workflows/job-deploy-windows.yml b/.github/workflows/job-deploy-windows.yml index aba12f4d1..8864fbc7e 100644 --- a/.github/workflows/job-deploy-windows.yml +++ b/.github/workflows/job-deploy-windows.yml @@ -38,13 +38,11 @@ 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 + environment: production env: AZURE_DEV_COLLECT_TELEMETRY: ${{ vars.AZURE_DEV_COLLECT_TELEMETRY }} PYTHONUTF8: 1 @@ -202,13 +200,17 @@ jobs: - name: Setup Azure Developer CLI (Windows) uses: Azure/setup-azd@v2 - - name: Login to AZD - id: login-azure + - name: Login to Azure (OIDC) + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Login to AZD (OIDC) shell: bash run: | - az login --service-principal -u ${{ secrets.AZURE_CLIENT_ID }} -p ${{ secrets.AZURE_CLIENT_SECRET }} --tenant ${{ secrets.AZURE_TENANT_ID }} - az account set --subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }} - azd auth login --client-id ${{ secrets.AZURE_CLIENT_ID }} --client-secret ${{ secrets.AZURE_CLIENT_SECRET }} --tenant-id ${{ secrets.AZURE_TENANT_ID }} + azd auth login --client-id ${{ secrets.AZURE_CLIENT_ID }} --federated-credential-provider github --tenant-id ${{ secrets.AZURE_TENANT_ID }} - name: Deploy using azd up and extract values (Windows) @@ -246,7 +248,8 @@ jobs: # Set ACR name only when building Docker image if ($env:BUILD_DOCKER_IMAGE -eq "true") { - $ACR_NAME = "${{ secrets.ACR_TEST_USERNAME }}" + # Extract ACR name from login server (e.g., myacr.azurecr.io -> myacr) + $ACR_NAME = ("${{ secrets.ACR_TEST_LOGIN_SERVER }}").Split('.')[0] azd env set ACR_NAME="$ACR_NAME" Write-Host "Set ACR name to: $ACR_NAME" } else { diff --git a/.github/workflows/job-deploy.yml b/.github/workflows/job-deploy.yml index f021b7d78..ef07ac896 100644 --- a/.github/workflows/job-deploy.yml +++ b/.github/workflows/job-deploy.yml @@ -77,7 +77,7 @@ on: value: ${{ jobs.azure-setup.outputs.RESOURCE_GROUP_NAME }} WEB_APPURL: description: "Container Web App URL" - value: ${{ jobs.deploy-linux.outputs.WEB_APPURL || jobs.deploy-windows.outputs.WEB_APPURL || jobs.deploy-devcontainer.outputs.WEB_APPURL }} + value: ${{ jobs.deploy-linux.outputs.WEB_APPURL || jobs.deploy-windows.outputs.WEB_APPURL }} ENV_NAME: description: "Environment Name" value: ${{ jobs.azure-setup.outputs.ENV_NAME }} @@ -103,16 +103,13 @@ 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 - packages: write # Required by job-deploy-devcontainer to push devcontainer image to GHCR - + jobs: azure-setup: name: Azure Setup if: inputs.trigger_type != 'workflow_dispatch' || inputs.existing_webapp_url == '' || inputs.existing_webapp_url == null runs-on: ubuntu-latest + environment: production outputs: RESOURCE_GROUP_NAME: ${{ steps.check_create_rg.outputs.RESOURCE_GROUP_NAME }} ENV_NAME: ${{ steps.generate_env_name.outputs.ENV_NAME }} @@ -155,7 +152,7 @@ jobs: fi # Validate runner_os (required - must be specific values) - ALLOWED_RUNNER_OS=("ubuntu-latest" "windows-latest" "devcontainer") + 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 @@ -325,18 +322,16 @@ jobs: - name: Checkout Code uses: actions/checkout@v4 - - name: Login to Azure - shell: bash - run: | - az login --service-principal -u ${{ secrets.AZURE_CLIENT_ID }} -p ${{ secrets.AZURE_CLIENT_SECRET }} --tenant ${{ secrets.AZURE_TENANT_ID }} - az account set --subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }} + - name: Login to Azure (OIDC) + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_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 }} IMAGE_MODEL_CHOICE: ${{ inputs.image_model_choice || 'gpt-image-1' }} @@ -676,21 +671,3 @@ jobs: AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }} AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }} secrets: inherit - - deploy-devcontainer: - name: Deploy on DevContainer - needs: azure-setup - if: inputs.runner_os == 'devcontainer' && !cancelled() && needs.azure-setup.result == 'success' - uses: ./.github/workflows/job-deploy-devcontainer.yml - with: - ENV_NAME: ${{ needs.azure-setup.outputs.ENV_NAME }} - AZURE_ENV_OPENAI_LOCATION: ${{ needs.azure-setup.outputs.AZURE_ENV_OPENAI_LOCATION }} - AZURE_LOCATION: ${{ needs.azure-setup.outputs.AZURE_LOCATION }} - RESOURCE_GROUP_NAME: ${{ needs.azure-setup.outputs.RESOURCE_GROUP_NAME }} - IMAGE_TAG: ${{ needs.azure-setup.outputs.IMAGE_TAG }} - BUILD_DOCKER_IMAGE: ${{ inputs.build_docker_image || 'false' }} - EXP: ${{ needs.azure-setup.outputs.EXP_ENABLED }} - WAF_ENABLED: ${{ inputs.waf_enabled == true && 'true' || 'false' }} - AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID: ${{ inputs.AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID }} - AZURE_EXISTING_AI_PROJECT_RESOURCE_ID: ${{ inputs.AZURE_EXISTING_AI_PROJECT_RESOURCE_ID }} - secrets: inherit diff --git a/.github/workflows/job-docker-build.yml b/.github/workflows/job-docker-build.yml index 41e581966..d14f0fe7c 100644 --- a/.github/workflows/job-docker-build.yml +++ b/.github/workflows/job-docker-build.yml @@ -19,14 +19,12 @@ 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 runs-on: ubuntu-latest + environment: production outputs: IMAGE_TAG: ${{ steps.generate_docker_tag.outputs.IMAGE_TAG }} steps: @@ -50,15 +48,19 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Log in to Azure Container Registry - uses: azure/docker-login@v2 + - name: Login to Azure (OIDC) + uses: azure/login@v2 with: - login-server: ${{ secrets.ACR_TEST_LOGIN_SERVER }} - username: ${{ secrets.ACR_TEST_USERNAME }} - password: ${{ secrets.ACR_TEST_PASSWORD }} + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Log in to Azure Container Registry + shell: bash + run: az acr login --name ${{ secrets.ACR_TEST_LOGIN_SERVER }} - - name: Build and Push Docker Image - id: build_push_image + - name: Build and Push Docker Image for Frontend Server + id: build_push_frontend uses: docker/build-push-action@v6 env: DOCKER_BUILD_SUMMARY: false @@ -67,13 +69,26 @@ jobs: file: ./content-gen/src/app/WebApp.Dockerfile push: true tags: | - ${{ secrets.ACR_TEST_LOGIN_SERVER }}/webapp:${{ steps.generate_docker_tag.outputs.IMAGE_TAG }} - ${{ secrets.ACR_TEST_LOGIN_SERVER }}/webapp:${{ steps.generate_docker_tag.outputs.IMAGE_TAG }}_${{ github.run_number }} + ${{ secrets.ACR_TEST_LOGIN_SERVER }}/content-gen-app:${{ steps.generate_docker_tag.outputs.IMAGE_TAG }} + ${{ secrets.ACR_TEST_LOGIN_SERVER }}/content-gen-app:${{ steps.generate_docker_tag.outputs.IMAGE_TAG }}_${{ github.run_number }} + + - name: Build and Push Docker Image for Backend Server + id: build_push_backend + uses: docker/build-push-action@v6 + env: + DOCKER_BUILD_SUMMARY: false + with: + context: ./content-gen/src/backend + file: ./content-gen/src/backend/ApiApp.Dockerfile + push: true + tags: | + ${{ secrets.ACR_TEST_LOGIN_SERVER }}/content-gen-api:${{ steps.generate_docker_tag.outputs.IMAGE_TAG }} + ${{ secrets.ACR_TEST_LOGIN_SERVER }}/content-gen-api:${{ steps.generate_docker_tag.outputs.IMAGE_TAG }}_${{ github.run_number }} - name: Verify Docker Image Build shell: bash run: | - echo "✅ Docker image successfully built and pushed" + echo "✅ Docker images successfully built and pushed" echo "Image tag: ${{ steps.generate_docker_tag.outputs.IMAGE_TAG }}" - name: Generate Docker Build Summary @@ -91,10 +106,11 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY if [[ "${{ job.status }}" == "success" ]]; then echo "### ✅ Build Details" >> $GITHUB_STEP_SUMMARY - echo "Successfully built and pushed one Docker image to ACR:" >> $GITHUB_STEP_SUMMARY + echo "Successfully built and pushed Docker images to ACR:" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Built Images:**" >> $GITHUB_STEP_SUMMARY - echo "- \`${ACR_NAME}.azurecr.io/webapp:${{ steps.generate_docker_tag.outputs.IMAGE_TAG }}\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${ACR_NAME}/content-gen-app:${{ steps.generate_docker_tag.outputs.IMAGE_TAG }}\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${ACR_NAME}/content-gen-api:${{ steps.generate_docker_tag.outputs.IMAGE_TAG }}\`" >> $GITHUB_STEP_SUMMARY else echo "### ❌ Build Failed" >> $GITHUB_STEP_SUMMARY echo "- Docker build process encountered an error" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/job-send-notification.yml b/.github/workflows/job-send-notification.yml index e5c833a33..e29a7655e 100644 --- a/.github/workflows/job-send-notification.yml +++ b/.github/workflows/job-send-notification.yml @@ -67,16 +67,13 @@ 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 continue-on-error: true env: - accelerator_name: "DocGen" + accelerator_name: "Content Gen" steps: - name: Validate Workflow Input Parameters shell: bash diff --git a/.github/workflows/test-automation-v2.yml b/.github/workflows/test-automation-v2.yml index 637a79fa6..5bc968623 100644 --- a/.github/workflows/test-automation-v2.yml +++ b/.github/workflows/test-automation-v2.yml @@ -24,13 +24,11 @@ env: url: ${{ inputs.DOCGEN_URL }} accelerator_name: "DocGen" test_suite: ${{ inputs.TEST_SUITE }} -permissions: - contents: read - actions: read - + jobs: test: runs-on: ubuntu-latest + environment: production outputs: TEST_SUCCESS: ${{ steps.test1.outcome == 'success' || steps.test2.outcome == 'success' || steps.test3.outcome == 'success' }} TEST_REPORT_URL: ${{ steps.upload_report.outputs.artifact-url }} @@ -43,10 +41,12 @@ jobs: with: python-version: '3.13' - - name: Login to Azure - run: | - az login --service-principal -u ${{ secrets.AZURE_CLIENT_ID }} -p ${{ secrets.AZURE_CLIENT_SECRET }} --tenant ${{ secrets.AZURE_TENANT_ID }} - az account set --subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }} + - name: Login to Azure (OIDC) + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - name: Install dependencies run: | diff --git a/content-gen/scripts/checkquota.sh b/content-gen/scripts/checkquota.sh index 21264c17f..1a7991ee0 100644 --- a/content-gen/scripts/checkquota.sh +++ b/content-gen/scripts/checkquota.sh @@ -14,12 +14,12 @@ # bash checkquota.sh none # # Usage (CI - via env vars): -# Set AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID, -# AZURE_SUBSCRIPTION_ID, GPT_MIN_CAPACITY, AZURE_REGIONS, IMAGE_MODEL_CHOICE +# Set AZURE_SUBSCRIPTION_ID, GPT_MIN_CAPACITY, AZURE_REGIONS, IMAGE_MODEL_CHOICE +# Authentication is handled externally via OIDC (az login already done before this script runs) # ============================================================================= -# ---- Determine run mode: CI (service principal) or Local (existing session) ---- -if [[ -n "$AZURE_CLIENT_ID" && -n "$AZURE_CLIENT_SECRET" && -n "$AZURE_TENANT_ID" ]]; then +# ---- Determine run mode: CI (pre-authenticated) or Local (existing session) ---- +if [[ -n "$AZURE_SUBSCRIPTION_ID" ]] && az account show &>/dev/null; then RUN_MODE="ci" else RUN_MODE="local" @@ -61,11 +61,7 @@ fi # ---- Authentication ---- if [[ "$RUN_MODE" == "ci" ]]; then - echo "🔑 Authenticating using Service Principal (CI mode)..." - if ! az login --service-principal -u "$AZURE_CLIENT_ID" -p "$AZURE_CLIENT_SECRET" --tenant "$AZURE_TENANT_ID"; then - echo "❌ Error: Failed to login using Service Principal." - exit 1 - fi + echo "🔑 Using pre-authenticated Azure CLI session (CI mode via OIDC)..." SUBSCRIPTION_ID="${AZURE_SUBSCRIPTION_ID}" if [[ -z "$SUBSCRIPTION_ID" || -z "$GPT_MIN_CAPACITY" ]]; then