Skip to content

infra: reduce idle timeouts from 60 to 30 minutes #115

infra: reduce idle timeouts from 60 to 30 minutes

infra: reduce idle timeouts from 60 to 30 minutes #115

Workflow file for this run

name: Infrastructure
on:
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy to'
type: choice
options:
- dev
- stage
default: dev
mode:
description: 'Deployment mode'
type: choice
options:
- deploy
- what-if
default: deploy
deployContainerApps:
description: 'Deploy Container Apps'
type: boolean
default: false
ghcrToken:
description: 'GitHub PAT for ghcr.io (optional — falls back to GHCR_TOKEN secret)'
type: string
default: ''
push:
branches: [main, develop]
paths:
- 'infra/**'
- '.github/workflows/infra.yml'
pull_request:
branches: [main, develop]
paths:
- 'infra/**'
- '.github/workflows/infra.yml'
jobs:
validate:
name: Validate Bicep
runs-on: ubuntu-latest
environment: dev
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v6
- name: Validate Bicep syntax
run: az bicep build --file infra/main.bicep
- name: Azure Login (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: ARM Validate (dev parameters)
run: |
az deployment sub validate \
--location eastus2 \
--template-file infra/main.bicep \
--parameters infra/main.parameters.dev.json \
--parameters servicePrincipalObjectId='00000000-0000-0000-0000-000000000000' \
--parameters deployAIModels=false
- name: ARM Validate (stage parameters)
run: |
az deployment sub validate \
--location eastus2 \
--template-file infra/main.bicep \
--parameters infra/main.parameters.stage.json \
--parameters servicePrincipalObjectId='00000000-0000-0000-0000-000000000000' \
--parameters deployAIModels=false
deploy-dev:
name: Deploy Infrastructure (Dev)
runs-on: ubuntu-latest
needs: validate
if: github.event_name != 'pull_request' && github.event.inputs.environment != 'stage'
environment: dev
concurrency:
group: infra-dev
cancel-in-progress: false
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v6
- name: Azure Login (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: Get Service Principal Object ID
id: sp
run: |
OBJECT_ID=$(az ad sp show --id ${{ secrets.AZURE_CLIENT_ID }} --query id -o tsv)
echo "object_id=$OBJECT_ID" >> $GITHUB_OUTPUT
- name: Deploy Infrastructure (What-If)
if: github.event.inputs.mode == 'what-if'
run: |
az deployment sub what-if \
--location eastus2 \
--template-file infra/main.bicep \
--parameters infra/main.parameters.dev.json \
--parameters servicePrincipalObjectId='${{ steps.sp.outputs.object_id }}' \
--parameters deployContainerApps=${{ github.event.inputs.deployContainerApps || 'false' }} \
--parameters ghcrToken='${{ github.event.inputs.ghcrToken || secrets.GHCR_TOKEN }}' \
--parameters deployAIModels=false
- name: Deploy Infrastructure
if: github.event.inputs.mode != 'what-if'
run: |
az deployment sub create \
--location eastus2 \
--name "sessionsight-infra-$(date +%Y%m%d-%H%M%S)" \
--template-file infra/main.bicep \
--parameters infra/main.parameters.dev.json \
--parameters servicePrincipalObjectId='${{ steps.sp.outputs.object_id }}' \
--parameters deployContainerApps=${{ github.event.inputs.deployContainerApps || (github.event_name == 'push' && 'true') || 'false' }} \
--parameters ghcrToken='${{ github.event.inputs.ghcrToken || secrets.GHCR_TOKEN }}' \
--parameters deployAIModels=false
- name: Provision Managed Identity SQL users
if: github.event.inputs.mode != 'what-if'
run: |
RG="rg-sessionsight-dev"
SQL_SERVER="sessionsight-sql-dev"
DB_NAME="sessionsight"
API_APP="sessionsight-dev-api"
CI_APP_ID=${{ secrets.AZURE_CLIENT_ID }}
az ad app permission add --id "${CI_APP_ID}" \
--api 00000003-0000-0000-c000-000000000000 \
--api-permissions 7ab1d382-f21e-4acd-a863-ba3e13f7da61=Role 2>/dev/null || true
az ad app permission admin-consent --id "${CI_APP_ID}" 2>/dev/null || true
MI_OBJECT_ID=$(az containerapp show -g ${RG} -n ${API_APP} --query "identity.principalId" -o tsv 2>/dev/null || true)
if [ -z "$MI_OBJECT_ID" ]; then
echo "Container App ${API_APP} not found or has no MI, skipping SQL user provisioning"
exit 0
fi
MI_CLIENT_ID=$(az ad sp show --id "${MI_OBJECT_ID}" --query appId -o tsv)
echo "MI objectId: ${MI_OBJECT_ID}"
echo "MI clientId: ${MI_CLIENT_ID}"
SID_HEX=$(python3 -c "import uuid; print('0x' + uuid.UUID('${MI_CLIENT_ID}').bytes_le.hex().upper())")
echo "SID hex: ${SID_HEX}"
curl -s https://packages.microsoft.com/keys/microsoft.asc | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc
sudo add-apt-repository "$(curl -s https://packages.microsoft.com/config/ubuntu/22.04/prod.list)"
sudo apt-get update -y && sudo apt-get install -y sqlcmd
sqlcmd -S "${SQL_SERVER}.database.windows.net" -d "${DB_NAME}" \
--authentication-method ActiveDirectoryDefault -b -V 11 \
-Q "IF EXISTS (SELECT 1 FROM sys.database_principals WHERE name = '${API_APP}')
DROP USER [${API_APP}];
CREATE USER [${API_APP}] WITH SID = ${SID_HEX}, TYPE = E;
ALTER ROLE db_owner ADD MEMBER [${API_APP}];
SELECT name, type_desc, sid FROM sys.database_principals WHERE name = '${API_APP}';"
echo "MI user [${API_APP}] provisioned in database [${DB_NAME}] with clientId ${MI_CLIENT_ID}"
- name: Show Deployment Outputs
if: github.event.inputs.mode != 'what-if'
run: |
echo "## Deployment Outputs" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
DEPLOYMENT_NAME=$(az deployment sub list \
--query "[?starts_with(name, 'sessionsight-infra-')].name | reverse(@) | [0]" \
-o tsv)
echo "Deployment: $DEPLOYMENT_NAME" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
az deployment sub show \
--name "$DEPLOYMENT_NAME" \
--query properties.outputs \
-o json >> $GITHUB_STEP_SUMMARY
deploy-stage:
name: Deploy Infrastructure (Stage)
runs-on: ubuntu-latest
needs: validate
if: github.event_name != 'pull_request' && (github.event.inputs.environment == 'stage' || (github.event_name == 'push' && github.ref == 'refs/heads/main'))
environment: stage
concurrency:
group: infra-stage
cancel-in-progress: false
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v6
- name: Azure Login (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: Get Service Principal Object ID
id: sp
run: |
OBJECT_ID=$(az ad sp show --id ${{ secrets.AZURE_CLIENT_ID }} --query id -o tsv)
echo "object_id=$OBJECT_ID" >> $GITHUB_OUTPUT
- name: Deploy Infrastructure (What-If)
if: github.event.inputs.mode == 'what-if'
run: |
az deployment sub what-if \
--location eastus2 \
--template-file infra/main.bicep \
--parameters infra/main.parameters.stage.json \
--parameters servicePrincipalObjectId='${{ steps.sp.outputs.object_id }}' \
--parameters deployAIModels=false
- name: Deploy Infrastructure
if: github.event.inputs.mode != 'what-if'
run: |
az deployment sub create \
--location eastus2 \
--name "sessionsight-infra-$(date +%Y%m%d-%H%M%S)" \
--template-file infra/main.bicep \
--parameters infra/main.parameters.stage.json \
--parameters servicePrincipalObjectId='${{ steps.sp.outputs.object_id }}' \
--parameters deployContainerApps=${{ github.event.inputs.deployContainerApps || (github.event_name == 'push' && 'true') || 'false' }} \
--parameters ghcrToken='${{ github.event.inputs.ghcrToken || secrets.GHCR_TOKEN }}' \
--parameters deployAIModels=false
- name: Provision Managed Identity SQL users
if: github.event.inputs.mode != 'what-if'
run: |
RG="rg-sessionsight-dev"
SQL_SERVER="sessionsight-sql-dev"
DB_NAME="sessionsight-stage"
API_APP="sessionsight-stage-api"
CI_APP_ID=${{ secrets.AZURE_CLIENT_ID }}
az ad app permission add --id "${CI_APP_ID}" \
--api 00000003-0000-0000-c000-000000000000 \
--api-permissions 7ab1d382-f21e-4acd-a863-ba3e13f7da61=Role 2>/dev/null || true
az ad app permission admin-consent --id "${CI_APP_ID}" 2>/dev/null || true
MI_OBJECT_ID=$(az containerapp show -g ${RG} -n ${API_APP} --query "identity.principalId" -o tsv 2>/dev/null || true)
if [ -z "$MI_OBJECT_ID" ]; then
echo "Container App ${API_APP} not found or has no MI, skipping SQL user provisioning"
exit 0
fi
MI_CLIENT_ID=$(az ad sp show --id "${MI_OBJECT_ID}" --query appId -o tsv)
echo "MI objectId: ${MI_OBJECT_ID}"
echo "MI clientId: ${MI_CLIENT_ID}"
SID_HEX=$(python3 -c "import uuid; print('0x' + uuid.UUID('${MI_CLIENT_ID}').bytes_le.hex().upper())")
echo "SID hex: ${SID_HEX}"
curl -s https://packages.microsoft.com/keys/microsoft.asc | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc
sudo add-apt-repository "$(curl -s https://packages.microsoft.com/config/ubuntu/22.04/prod.list)"
sudo apt-get update -y && sudo apt-get install -y sqlcmd
sqlcmd -S "${SQL_SERVER}.database.windows.net" -d "${DB_NAME}" \
--authentication-method ActiveDirectoryDefault -b -V 11 \
-Q "IF EXISTS (SELECT 1 FROM sys.database_principals WHERE name = '${API_APP}')
DROP USER [${API_APP}];
CREATE USER [${API_APP}] WITH SID = ${SID_HEX}, TYPE = E;
ALTER ROLE db_owner ADD MEMBER [${API_APP}];
SELECT name, type_desc, sid FROM sys.database_principals WHERE name = '${API_APP}';"
echo "MI user [${API_APP}] provisioned in database [${DB_NAME}] with clientId ${MI_CLIENT_ID}"
- name: Show Deployment Outputs
if: github.event.inputs.mode != 'what-if'
run: |
echo "## Deployment Outputs" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
DEPLOYMENT_NAME=$(az deployment sub list \
--query "[?starts_with(name, 'sessionsight-infra-')].name | reverse(@) | [0]" \
-o tsv)
echo "Deployment: $DEPLOYMENT_NAME" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
az deployment sub show \
--name "$DEPLOYMENT_NAME" \
--query properties.outputs \
-o json >> $GITHUB_STEP_SUMMARY
preview:
name: Preview Changes (PR)
runs-on: ubuntu-latest
needs: validate
# Only run what-if on PRs
if: github.event_name == 'pull_request'
environment: dev
permissions:
id-token: write
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v6
- name: Azure Login (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: Get Service Principal Object ID
id: sp
run: |
OBJECT_ID=$(az ad sp show --id ${{ secrets.AZURE_CLIENT_ID }} --query id -o tsv)
echo "object_id=$OBJECT_ID" >> $GITHUB_OUTPUT
- name: Infrastructure What-If (Dev)
run: |
az deployment sub what-if \
--location eastus2 \
--template-file infra/main.bicep \
--parameters infra/main.parameters.dev.json \
--parameters servicePrincipalObjectId='${{ steps.sp.outputs.object_id }}' \
--parameters deployAIModels=false \
--no-pretty-print 2>&1 | tail -200 > /tmp/whatif-dev.txt || true
- name: Infrastructure What-If (Stage)
run: |
az deployment sub what-if \
--location eastus2 \
--template-file infra/main.bicep \
--parameters infra/main.parameters.stage.json \
--parameters servicePrincipalObjectId='${{ steps.sp.outputs.object_id }}' \
--parameters deployAIModels=false \
--no-pretty-print 2>&1 | tail -200 > /tmp/whatif-stage.txt || true
- name: Comment PR with What-If Results
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
const devOutput = fs.readFileSync('/tmp/whatif-dev.txt', 'utf8').trim();
const stageOutput = fs.readFileSync('/tmp/whatif-stage.txt', 'utf8').trim();
const body = `### Infrastructure Changes Preview
#### Dev Environment
\`\`\`
${devOutput}
\`\`\`
#### Stage Environment
\`\`\`
${stageOutput}
\`\`\`
*Pushed by: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`;
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body
});