From 99107e5cf83f8a94a58b311954a5a0a2c218c4f2 Mon Sep 17 00:00:00 2001 From: souvikghosh04 Date: Wed, 17 Dec 2025 11:24:12 +0530 Subject: [PATCH 1/7] Checkin sample DAB deployment scripts --- .../azure-container-apps-dab-starter.ps1 | 537 ++++++++++++++++++ .../azure-container-instances-dab-starter.ps1 | 535 +++++++++++++++++ samples/azure/readme.md | 238 +++++++- 3 files changed, 1306 insertions(+), 4 deletions(-) create mode 100644 samples/azure/azure-container-apps-dab-starter.ps1 create mode 100644 samples/azure/azure-container-instances-dab-starter.ps1 diff --git a/samples/azure/azure-container-apps-dab-starter.ps1 b/samples/azure/azure-container-apps-dab-starter.ps1 new file mode 100644 index 0000000000..dedf5af219 --- /dev/null +++ b/samples/azure/azure-container-apps-dab-starter.ps1 @@ -0,0 +1,537 @@ +<# +.SYNOPSIS + Deploy Data API builder (DAB) to Azure Container Apps with MS SQL Server and test data. + +.DESCRIPTION + This script automates the deployment of Data API builder to Azure Container Apps. + It creates all required Azure resources including: + - Resource Group + - Azure Container Registry (ACR) + - MS SQL Server and Database + - Container Apps Environment + - Container App with DAB + - Loads test data from DatabaseSchema-MsSql.sql + - Configures firewall rules for secure access + - Generates DAB configuration file + +.PARAMETER SubscriptionId + Azure subscription ID. If not provided, uses the current active subscription. + +.PARAMETER ResourceGroup + Name of the resource group. Auto-generates if not provided. + +.PARAMETER Location + Azure region (e.g., eastus, westus2). Default: eastus + +.PARAMETER ResourcePrefix + Prefix for resource names. Auto-generates if not provided. + +.PARAMETER SqlAdminPassword + SQL Server admin password. Auto-generates a secure password if not provided. + +.PARAMETER SkipCleanup + If set, resources won't be deleted on errors. + +.PARAMETER ContainerPort + Port for the DAB container. Default: 5000 + +.PARAMETER SqlServiceTier + SQL Database service tier. Default: S0 + +.PARAMETER MinReplicas + Minimum number of container replicas. Default: 1 + +.PARAMETER MaxReplicas + Maximum number of container replicas. Default: 3 + +.EXAMPLE + .\azure-container-apps-dab-starter.ps1 + Runs with auto-generated values and prompts for confirmation + +.EXAMPLE + .\azure-container-apps-dab-starter.ps1 -ResourcePrefix "mydab" -Location "westus2" + Deploys to West US 2 with custom prefix + +.EXAMPLE + .\azure-container-apps-dab-starter.ps1 -SkipCleanup + Deploys and keeps resources even if errors occur + +.NOTES + Prerequisites: + - Azure CLI installed and logged in + - Docker Desktop installed and running + - sqlcmd utility installed (SQL Server Command Line Tools) + - PowerShell 5.1 or higher + - Sufficient permissions in Azure subscription +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory=$false)] + [string]$SubscriptionId, + + [Parameter(Mandatory=$false)] + [string]$ResourceGroup, + + [Parameter(Mandatory=$false)] + [ValidateSet('eastus', 'eastus2', 'westus', 'westus2', 'westus3', 'centralus', 'northeurope', 'westeurope', 'uksouth', 'southeastasia')] + [string]$Location = "eastus", + + [Parameter(Mandatory=$false)] + [string]$ResourcePrefix, + + [Parameter(Mandatory=$false)] + [SecureString]$SqlAdminPassword, + + [Parameter(Mandatory=$false)] + [switch]$SkipCleanup, + + [Parameter(Mandatory=$false)] + [int]$ContainerPort = 5000, + + [Parameter(Mandatory=$false)] + [ValidateSet('Basic', 'S0', 'S1', 'S2', 'P1', 'P2')] + [string]$SqlServiceTier = "S0", + + [Parameter(Mandatory=$false)] + [ValidateRange(0, 30)] + [int]$MinReplicas = 1, + + [Parameter(Mandatory=$false)] + [ValidateRange(1, 30)] + [int]$MaxReplicas = 3 +) + +$ErrorActionPreference = "Stop" +$ProgressPreference = "SilentlyContinue" + +# ============================================================================ +# HELPER FUNCTIONS +# ============================================================================ + +function Write-Step { + param([string]$Message) + Write-Host "`n==> $Message" -ForegroundColor Cyan +} + +function Write-Success { + param([string]$Message) + Write-Host "[OK] $Message" -ForegroundColor Green +} + +function Write-Error { + param([string]$Message) + Write-Host "[ERROR] $Message" -ForegroundColor Red +} + +function Test-Prerequisites { + Write-Step "Checking prerequisites..." + + # Check Azure CLI + if (-not (Get-Command az -ErrorAction SilentlyContinue)) { + Write-Error "Azure CLI is not installed. Please install from: https://aka.ms/InstallAzureCLIDirect" + exit 1 + } + Write-Success "Azure CLI installed" + + # Check Docker + if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { + Write-Error "Docker is not installed. Please install Docker Desktop from: https://www.docker.com/products/docker-desktop" + exit 1 + } + + # Check if Docker is running + try { + docker ps | Out-Null + Write-Success "Docker is running" + } + catch { + Write-Error "Docker is not running. Please start Docker Desktop." + exit 1 + } + + # Check sqlcmd + if (-not (Get-Command sqlcmd -ErrorAction SilentlyContinue)) { + Write-Warning "sqlcmd is not installed. Installing SQL Server Command Line Tools..." + Write-Host "Please install from: https://aka.ms/sqlcmd" -ForegroundColor Yellow + Write-Host "Or run: winget install Microsoft.SqlServer.SqlCmd" -ForegroundColor Yellow + exit 1 + } + Write-Success "sqlcmd installed" + + # Check if logged into Azure + $account = az account show 2>$null | ConvertFrom-Json + if (-not $account) { + Write-Error "Not logged into Azure. Please run: az login" + exit 1 + } + Write-Success "Logged into Azure as $($account.user.name)" +} + +function Get-UniqueResourceName { + param([string]$Prefix, [string]$Type) + $random = -join ((48..57) + (97..122) | Get-Random -Count 6 | ForEach-Object {[char]$_}) + return "$Prefix-$Type-$random".ToLower() +} + +function New-RandomPassword { + $length = 16 + $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()' + $password = -join ((1..$length) | ForEach-Object { $chars[(Get-Random -Maximum $chars.Length)] }) + # Ensure complexity requirements + $password = 'A1!' + $password + return $password +} + +function New-DabConfigFile { + param( + [string]$SqlServer, + [string]$SqlDatabase, + [string]$SqlUser, + [string]$SqlPassword, + [string]$OutputPath + ) + + # Build config as JSON string directly to avoid PowerShell hashtable issues + $configJson = @' +{ + "$schema": "https://github.com/Azure/data-api-builder/releases/latest/download/dab.draft.schema.json", + "data-source": { + "database-type": "mssql", + "connection-string": "@env('DATABASE_CONNECTION_STRING')" + }, + "runtime": { + "rest": { + "enabled": true, + "path": "/api" + }, + "graphql": { + "enabled": true, + "path": "/graphql", + "allow-introspection": true + }, + "host": { + "mode": "production", + "cors": { + "origins": ["*"], + "allow-credentials": false + } + } + }, + "entities": { + "Book": { + "source": "books", + "permissions": [ + { + "role": "anonymous", + "actions": ["create", "read", "update", "delete"] + } + ] + }, + "Publisher": { + "source": "publishers", + "permissions": [ + { + "role": "anonymous", + "actions": ["create", "read", "update", "delete"] + } + ] + }, + "Author": { + "source": "authors", + "permissions": [ + { + "role": "anonymous", + "actions": ["create", "read", "update", "delete"] + } + ] + } + } +} +'@ + + $configJson | Set-Content -Path $OutputPath + Write-Success "Generated DAB config file: $OutputPath" +} + +# ============================================================================ +# MAIN SCRIPT +# ============================================================================ + +Write-Host "`n" -ForegroundColor Blue +Write-Host "==========================================================================" -ForegroundColor Blue +Write-Host " Data API Builder - Azure Container Apps Deployment" -ForegroundColor Blue +Write-Host "==========================================================================" -ForegroundColor Blue +Write-Host "" + +# Check prerequisites +Test-Prerequisites + +# Find repo root +$repoPath = $PSScriptRoot +while ($repoPath -and -not (Test-Path (Join-Path $repoPath "Dockerfile"))) { + $repoPath = Split-Path $repoPath -Parent +} + +if (-not $repoPath -or -not (Test-Path (Join-Path $repoPath "Dockerfile"))) { + Write-Error "Could not find data-api-builder repository root (looking for Dockerfile)" + exit 1 +} + +$sqlScriptPath = Join-Path $repoPath "src\Service.Tests\DatabaseSchema-MsSql.sql" +if (-not (Test-Path $sqlScriptPath)) { + Write-Error "Could not find SQL script at: $sqlScriptPath" + exit 1 +} + +Write-Success "Repository root: $repoPath" + +# Generate or validate parameters +if (-not $ResourcePrefix) { + $ResourcePrefix = "dab$(Get-Random -Maximum 9999)" + Write-Host "Generated resource prefix: $ResourcePrefix" -ForegroundColor Yellow +} + +if (-not $ResourceGroup) { + $ResourceGroup = "rg-$ResourcePrefix" + Write-Host "Generated resource group: $ResourceGroup" -ForegroundColor Yellow +} + +# Set subscription +if ($SubscriptionId) { + Write-Step "Setting Azure subscription..." + az account set --subscription $SubscriptionId + Write-Success "Subscription set to: $SubscriptionId" +} +else { + $currentSub = az account show | ConvertFrom-Json + $SubscriptionId = $currentSub.id + Write-Host "Using current subscription: $($currentSub.name) ($SubscriptionId)" -ForegroundColor Yellow +} + +# Generate resource names +$acrName = Get-UniqueResourceName -Prefix $ResourcePrefix -Type "acr" +$acrName = $acrName -replace "[^a-zA-Z0-9]", "" # ACR names must be alphanumeric +$acaName = Get-UniqueResourceName -Prefix $ResourcePrefix -Type "aca" +$sqlServerName = Get-UniqueResourceName -Prefix $ResourcePrefix -Type "sql" +$sqlDbName = "dabdb" +$sqlAdminUser = "sqladmin" +$envName = "${acaName}-env" +$acrImageName = "dab" +$acrImageTag = "latest" + +# Generate SQL password if not provided +if (-not $SqlAdminPassword) { + $generatedPassword = New-RandomPassword + $SqlAdminPassword = ConvertTo-SecureString -String $generatedPassword -AsPlainText -Force + Write-Host "Generated SQL Server admin password (save this): $generatedPassword" -ForegroundColor Yellow +} + +$sqlAdminPasswordPlain = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( + [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SqlAdminPassword) +) + +# Summary +Write-Host "`nDeployment Configuration:" -ForegroundColor Cyan +Write-Host " Subscription: $SubscriptionId" +Write-Host " Resource Group: $ResourceGroup" +Write-Host " Location: $Location" +Write-Host " ACR Name: $acrName" +Write-Host " Container App: $acaName" +Write-Host " SQL Server: $sqlServerName" +Write-Host " SQL Database: $sqlDbName" +Write-Host " SQL Admin User: $sqlAdminUser" +Write-Host " Container Port: $ContainerPort" +Write-Host " Min Replicas: $MinReplicas" +Write-Host " Max Replicas: $MaxReplicas" + +$confirmation = Read-Host "`nProceed with deployment? (y/N)" +if ($confirmation -ne 'y') { + Write-Host "Deployment cancelled." -ForegroundColor Yellow + exit 0 +} + +try { + # Create Resource Group + Write-Step "Creating resource group..." + az group create --name $ResourceGroup --location $Location | Out-Null + Write-Success "Resource group created: $ResourceGroup" + + # Create ACR + Write-Step "Creating Azure Container Registry..." + az acr create ` + --resource-group $ResourceGroup ` + --name $acrName ` + --sku Basic ` + --admin-enabled true | Out-Null + Write-Success "ACR created: $acrName" + + $acrLoginServer = az acr show --name $acrName --query "loginServer" -o tsv + $acrPassword = az acr credential show --name $acrName --query "passwords[0].value" -o tsv + + # Build and Push Docker Image + Write-Step "Building Docker image... (this may take a few minutes)" + docker build -f "$repoPath\Dockerfile" -t "${acrImageName}:${acrImageTag}" $repoPath + Write-Success "Docker image built" + + Write-Step "Pushing image to ACR..." + docker tag "${acrImageName}:${acrImageTag}" "${acrLoginServer}/${acrImageName}:${acrImageTag}" + az acr login --name $acrName | Out-Null + docker push "${acrLoginServer}/${acrImageName}:${acrImageTag}" + Write-Success "Image pushed to ACR" + + # Create SQL Server + Write-Step "Creating SQL Server..." + az sql server create ` + --name $sqlServerName ` + --resource-group $ResourceGroup ` + --location $Location ` + --admin-user $sqlAdminUser ` + --admin-password $sqlAdminPasswordPlain | Out-Null + Write-Success "SQL Server created: $sqlServerName" + + # Create SQL Database + Write-Step "Creating SQL Database..." + az sql db create ` + --resource-group $ResourceGroup ` + --server $sqlServerName ` + --name $sqlDbName ` + --service-objective $SqlServiceTier | Out-Null + Write-Success "SQL Database created: $sqlDbName" + + # Configure firewall rules + Write-Step "Configuring SQL Server firewall rules..." + + # Allow Azure services + az sql server firewall-rule create ` + --resource-group $ResourceGroup ` + --server $sqlServerName ` + --name "AllowAzureServices" ` + --start-ip-address 0.0.0.0 ` + --end-ip-address 0.0.0.0 | Out-Null + + # Allow client IP + $myIp = (Invoke-WebRequest -Uri "https://api.ipify.org" -UseBasicParsing).Content.Trim() + az sql server firewall-rule create ` + --resource-group $ResourceGroup ` + --server $sqlServerName ` + --name "ClientIP" ` + --start-ip-address $myIp ` + --end-ip-address $myIp | Out-Null + + Write-Success "Firewall rules configured (Azure services + your IP: $myIp)" + + # Load test data + Write-Step "Loading test data into database..." + $sqlServer = "$sqlServerName.database.windows.net" + + # Wait a moment for SQL server to be fully ready + Start-Sleep -Seconds 10 + + try { + sqlcmd -S $sqlServer -U $sqlAdminUser -P $sqlAdminPasswordPlain -d $sqlDbName -i $sqlScriptPath -b + Write-Success "Test data loaded successfully" + } + catch { + Write-Warning "Failed to load test data. You may need to run it manually." + Write-Host "Command: sqlcmd -S $sqlServer -U $sqlAdminUser -P *** -d $sqlDbName -i $sqlScriptPath" -ForegroundColor Yellow + } + + # Generate DAB config + Write-Step "Generating DAB configuration..." + $dabConfigPath = Join-Path $env:TEMP "dab-config.json" + New-DabConfigFile -SqlServer $sqlServer -SqlDatabase $sqlDbName -SqlUser $sqlAdminUser -SqlPassword $sqlAdminPasswordPlain -OutputPath $dabConfigPath + + # Create Container Apps Environment + Write-Step "Creating Container Apps Environment..." + az containerapp env create ` + --name $envName ` + --resource-group $ResourceGroup ` + --location $Location | Out-Null + Write-Success "Container Apps Environment created" + + # Create connection string + $connectionString = "Server=$sqlServer;Database=$sqlDbName;User ID=$sqlAdminUser;Password=$sqlAdminPasswordPlain;TrustServerCertificate=true;" + + # Deploy Container App + Write-Step "Deploying Container App..." + az containerapp create ` + --name $acaName ` + --resource-group $ResourceGroup ` + --environment $envName ` + --image "${acrLoginServer}/${acrImageName}:${acrImageTag}" ` + --target-port $ContainerPort ` + --ingress external ` + --transport auto ` + --min-replicas $MinReplicas ` + --max-replicas $MaxReplicas ` + --cpu 0.5 ` + --memory 1.0Gi ` + --registry-server $acrLoginServer ` + --registry-username $acrName ` + --registry-password $acrPassword ` + --secrets "db-connection-string=$connectionString" ` + --env-vars "ASPNETCORE_URLS=http://+:$ContainerPort" "DATABASE_CONNECTION_STRING=secretref:db-connection-string" | Out-Null + + Write-Success "Container App deployed" + + # Get app URL + $appUrl = az containerapp show ` + --name $acaName ` + --resource-group $resourceGroup ` + --query "properties.configuration.ingress.fqdn" -o tsv + + # Display summary + Write-Host "`n" -ForegroundColor Green + Write-Host "==========================================================================" -ForegroundColor Green + Write-Host " DEPLOYMENT SUCCESSFUL!" -ForegroundColor Green + Write-Host "==========================================================================" -ForegroundColor Green + Write-Host "" + + Write-Host "Resource Details:" -ForegroundColor Cyan + Write-Host " Resource Group: $ResourceGroup" + Write-Host " Location: $Location" + Write-Host "" + Write-Host "DAB Endpoints:" -ForegroundColor Cyan + Write-Host " App URL: https://$appUrl" + Write-Host " REST API: https://$appUrl/api" + Write-Host " GraphQL: https://$appUrl/graphql" + Write-Host " Health Check: https://$appUrl/health" + Write-Host "" + Write-Host "Database Connection:" -ForegroundColor Cyan + Write-Host " Server: $sqlServer" + Write-Host " Database: $sqlDbName" + Write-Host " Admin User: $sqlAdminUser" + Write-Host " Admin Password: $sqlAdminPasswordPlain" + Write-Host "" + Write-Host "Try these commands:" -ForegroundColor Cyan + Write-Host " # List all publishers" + Write-Host " curl https://$appUrl/api/Publisher" + Write-Host "" + Write-Host " # GraphQL query" + $curlCmd = ' curl https://{0}/graphql -H "Content-Type: application/json" -d "{\"query\":\"{{publishers{{items{{id name}}}}}}\""' -f $appUrl + Write-Host $curlCmd + Write-Host "" + Write-Host "Configuration file generated at: $dabConfigPath" -ForegroundColor Yellow + Write-Host "" + Write-Host "To delete all resources, run:" -ForegroundColor Yellow + Write-Host " az group delete --name $ResourceGroup --yes --no-wait" + Write-Host "" + +} +catch { + Write-Error "Deployment failed: $_" + Write-Host $_.Exception.Message -ForegroundColor Red + Write-Host $_.ScriptStackTrace -ForegroundColor Red + + if (-not $SkipCleanup) { + $cleanup = Read-Host "`nDelete created resources? (y/N)" + if ($cleanup -eq 'y') { + Write-Step "Cleaning up resources..." + az group delete --name $ResourceGroup --yes --no-wait + Write-Host "Cleanup initiated (running in background)" -ForegroundColor Yellow + } + } + + exit 1 +} \ No newline at end of file diff --git a/samples/azure/azure-container-instances-dab-starter.ps1 b/samples/azure/azure-container-instances-dab-starter.ps1 new file mode 100644 index 0000000000..a855e39528 --- /dev/null +++ b/samples/azure/azure-container-instances-dab-starter.ps1 @@ -0,0 +1,535 @@ +<# +.SYNOPSIS + Deploy Data API builder (DAB) to Azure Container Instances with MS SQL Server and test data. + +.DESCRIPTION + This script automates the deployment of Data API builder to Azure Container Instances. + It creates all required Azure resources including: + - Resource Group + - Azure Container Registry (ACR) + - MS SQL Server and Database + - Container Instance with DAB + - Loads test data from DatabaseSchema-MsSql.sql + - Configures firewall rules for secure access + - Generates DAB configuration file + +.PARAMETER SubscriptionId + Azure subscription ID. If not provided, uses the current active subscription. + +.PARAMETER ResourceGroup + Name of the resource group. Auto-generates if not provided. + +.PARAMETER Location + Azure region (e.g., eastus, westus2). Default: eastus + +.PARAMETER ResourcePrefix + Prefix for resource names. Auto-generates if not provided. + +.PARAMETER SqlAdminPassword + SQL Server admin password. Auto-generates a secure password if not provided. + +.PARAMETER SkipCleanup + If set, resources won't be deleted on errors. + +.PARAMETER ContainerPort + Port for the DAB container. Default: 5000 + +.PARAMETER SqlServiceTier + SQL Database service tier. Default: S0 + +.PARAMETER ContainerCpu + Number of CPU cores for the container. Default: 1 + +.PARAMETER ContainerMemory + Memory in GB for the container. Default: 1.5 + +.EXAMPLE + .\azure-container-instances-dab-starter.ps1 + Runs with auto-generated values and prompts for confirmation + +.EXAMPLE + .\azure-container-instances-dab-starter.ps1 -ResourcePrefix "mydab" -Location "westus2" + Deploys to West US 2 with custom prefix + +.EXAMPLE + .\azure-container-instances-dab-starter.ps1 -ContainerCpu 2 -ContainerMemory 3 + Deploys with custom CPU and memory settings + +.NOTES + Prerequisites: + - Azure CLI installed and logged in + - Docker Desktop installed and running + - sqlcmd utility installed (SQL Server Command Line Tools) + - PowerShell 5.1 or higher + - Sufficient permissions in Azure subscription +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory=$false)] + [string]$SubscriptionId, + + [Parameter(Mandatory=$false)] + [string]$ResourceGroup, + + [Parameter(Mandatory=$false)] + [ValidateSet('eastus', 'eastus2', 'westus', 'westus2', 'westus3', 'centralus', 'northeurope', 'westeurope', 'uksouth', 'southeastasia')] + [string]$Location = "eastus", + + [Parameter(Mandatory=$false)] + [string]$ResourcePrefix, + + [Parameter(Mandatory=$false)] + [SecureString]$SqlAdminPassword, + + [Parameter(Mandatory=$false)] + [switch]$SkipCleanup, + + [Parameter(Mandatory=$false)] + [int]$ContainerPort = 5000, + + [Parameter(Mandatory=$false)] + [ValidateSet('Basic', 'S0', 'S1', 'S2', 'P1', 'P2')] + [string]$SqlServiceTier = "S0", + + [Parameter(Mandatory=$false)] + [ValidateRange(1, 4)] + [int]$ContainerCpu = 1, + + [Parameter(Mandatory=$false)] + [ValidateRange(0.5, 16)] + [double]$ContainerMemory = 1.5 +) + +$ErrorActionPreference = "Stop" +$ProgressPreference = "SilentlyContinue" + +# ============================================================================ +# HELPER FUNCTIONS +# ============================================================================ + +function Write-Step { + param([string]$Message) + Write-Host "`n==> $Message" -ForegroundColor Cyan +} + +function Write-Success { + param([string]$Message) + Write-Host "[OK] $Message" -ForegroundColor Green +} + +function Write-Error { + param([string]$Message) + Write-Host "[ERROR] $Message" -ForegroundColor Red +} + +function Test-Prerequisites { + Write-Step "Checking prerequisites..." + + # Check Azure CLI + if (-not (Get-Command az -ErrorAction SilentlyContinue)) { + Write-Error "Azure CLI is not installed. Please install from: https://aka.ms/InstallAzureCLIDirect" + exit 1 + } + Write-Success "Azure CLI installed" + + # Check Docker + if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { + Write-Error "Docker is not installed. Please install Docker Desktop from: https://www.docker.com/products/docker-desktop" + exit 1 + } + + # Check if Docker is running + try { + docker ps | Out-Null + Write-Success "Docker is running" + } + catch { + Write-Error "Docker is not running. Please start Docker Desktop." + exit 1 + } + + # Check sqlcmd + if (-not (Get-Command sqlcmd -ErrorAction SilentlyContinue)) { + Write-Warning "sqlcmd is not installed. Installing SQL Server Command Line Tools..." + Write-Host "Please install from: https://aka.ms/sqlcmd" -ForegroundColor Yellow + Write-Host "Or run: winget install Microsoft.SqlServer.SqlCmd" -ForegroundColor Yellow + exit 1 + } + Write-Success "sqlcmd installed" + + # Check if logged into Azure + $account = az account show 2>$null | ConvertFrom-Json + if (-not $account) { + Write-Error "Not logged into Azure. Please run: az login" + exit 1 + } + Write-Success "Logged into Azure as $($account.user.name)" +} + +function Get-UniqueResourceName { + param([string]$Prefix, [string]$Type) + $random = -join ((48..57) + (97..122) | Get-Random -Count 6 | ForEach-Object {[char]$_}) + return "$Prefix-$Type-$random".ToLower() +} + +function New-RandomPassword { + $length = 16 + $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()' + $password = -join ((1..$length) | ForEach-Object { $chars[(Get-Random -Maximum $chars.Length)] }) + # Ensure complexity requirements + $password = 'A1!' + $password + return $password +} + +function New-DabConfigFile { + param( + [string]$SqlServer, + [string]$SqlDatabase, + [string]$SqlUser, + [string]$SqlPassword, + [string]$OutputPath + ) + + # Build config as JSON string directly to avoid PowerShell hashtable issues + $configJson = @' +{ + "$schema": "https://github.com/Azure/data-api-builder/releases/latest/download/dab.draft.schema.json", + "data-source": { + "database-type": "mssql", + "connection-string": "@env('DATABASE_CONNECTION_STRING')" + }, + "runtime": { + "rest": { + "enabled": true, + "path": "/api" + }, + "graphql": { + "enabled": true, + "path": "/graphql", + "allow-introspection": true + }, + "host": { + "mode": "production", + "cors": { + "origins": ["*"], + "allow-credentials": false + } + } + }, + "entities": { + "Book": { + "source": "books", + "permissions": [ + { + "role": "anonymous", + "actions": ["create", "read", "update", "delete"] + } + ] + }, + "Publisher": { + "source": "publishers", + "permissions": [ + { + "role": "anonymous", + "actions": ["create", "read", "update", "delete"] + } + ] + }, + "Author": { + "source": "authors", + "permissions": [ + { + "role": "anonymous", + "actions": ["create", "read", "update", "delete"] + } + ] + } + } +} +'@ + + $configJson | Set-Content -Path $OutputPath + Write-Success "Generated DAB config file: $OutputPath" +} + +# ============================================================================ +# MAIN SCRIPT +# ============================================================================ + +Write-Host "`n" -ForegroundColor Blue +Write-Host "==========================================================================" -ForegroundColor Blue +Write-Host " Data API Builder - Azure Container Instances Deployment" -ForegroundColor Blue +Write-Host "==========================================================================" -ForegroundColor Blue +Write-Host "" + +# Check prerequisites +Test-Prerequisites + +# Find repo root +$repoPath = $PSScriptRoot +while ($repoPath -and -not (Test-Path (Join-Path $repoPath "Dockerfile"))) { + $repoPath = Split-Path $repoPath -Parent +} + +if (-not $repoPath -or -not (Test-Path (Join-Path $repoPath "Dockerfile"))) { + Write-Error "Could not find data-api-builder repository root (looking for Dockerfile)" + exit 1 +} + +$sqlScriptPath = Join-Path $repoPath "src\Service.Tests\DatabaseSchema-MsSql.sql" +if (-not (Test-Path $sqlScriptPath)) { + Write-Error "Could not find SQL script at: $sqlScriptPath" + exit 1 +} + +Write-Success "Repository root: $repoPath" + +# Generate or validate parameters +if (-not $ResourcePrefix) { + $ResourcePrefix = "dab$(Get-Random -Maximum 9999)" + Write-Host "Generated resource prefix: $ResourcePrefix" -ForegroundColor Yellow +} + +if (-not $ResourceGroup) { + $ResourceGroup = "rg-$ResourcePrefix" + Write-Host "Generated resource group: $ResourceGroup" -ForegroundColor Yellow +} + +# Set subscription +if ($SubscriptionId) { + Write-Step "Setting Azure subscription..." + az account set --subscription $SubscriptionId + Write-Success "Subscription set to: $SubscriptionId" +} +else { + $currentSub = az account show | ConvertFrom-Json + $SubscriptionId = $currentSub.id + Write-Host "Using current subscription: $($currentSub.name) ($SubscriptionId)" -ForegroundColor Yellow +} + +# Generate resource names +$acrName = Get-UniqueResourceName -Prefix $ResourcePrefix -Type "acr" +$acrName = $acrName -replace "[^a-zA-Z0-9]", "" # ACR names must be alphanumeric +$aciName = Get-UniqueResourceName -Prefix $ResourcePrefix -Type "aci" +$sqlServerName = Get-UniqueResourceName -Prefix $ResourcePrefix -Type "sql" +$sqlDbName = "dabdb" +$sqlAdminUser = "sqladmin" +$dnsLabel = Get-UniqueResourceName -Prefix $ResourcePrefix -Type "dab" +$dnsLabel = $dnsLabel -replace "[^a-zA-Z0-9-]", "" # DNS labels must be alphanumeric with hyphens +$acrImageName = "dab" +$acrImageTag = "latest" + +# Generate SQL password if not provided +if (-not $SqlAdminPassword) { + $generatedPassword = New-RandomPassword + $SqlAdminPassword = ConvertTo-SecureString -String $generatedPassword -AsPlainText -Force + Write-Host "Generated SQL Server admin password (save this): $generatedPassword" -ForegroundColor Yellow +} + +$sqlAdminPasswordPlain = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( + [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SqlAdminPassword) +) + +# Summary +Write-Host "`nDeployment Configuration:" -ForegroundColor Cyan +Write-Host " Subscription: $SubscriptionId" +Write-Host " Resource Group: $ResourceGroup" +Write-Host " Location: $Location" +Write-Host " ACR Name: $acrName" +Write-Host " Container Name: $aciName" +Write-Host " DNS Label: $dnsLabel" +Write-Host " SQL Server: $sqlServerName" +Write-Host " SQL Database: $sqlDbName" +Write-Host " SQL Admin User: $sqlAdminUser" +Write-Host " Container Port: $ContainerPort" +Write-Host " Container CPU: $ContainerCpu cores" +Write-Host " Container Memory: $ContainerMemory GB" + +$confirmation = Read-Host "`nProceed with deployment? (y/N)" +if ($confirmation -ne 'y') { + Write-Host "Deployment cancelled." -ForegroundColor Yellow + exit 0 +} + +try { + # Create Resource Group + Write-Step "Creating resource group..." + az group create --name $ResourceGroup --location $Location | Out-Null + Write-Success "Resource group created: $ResourceGroup" + + # Create ACR + Write-Step "Creating Azure Container Registry..." + az acr create ` + --resource-group $ResourceGroup ` + --name $acrName ` + --sku Basic ` + --admin-enabled true | Out-Null + Write-Success "ACR created: $acrName" + + $acrLoginServer = az acr show --name $acrName --query "loginServer" -o tsv + $acrPassword = az acr credential show --name $acrName --query "passwords[0].value" -o tsv + + # Build and Push Docker Image + Write-Step "Building Docker image... (this may take a few minutes)" + docker build -f "$repoPath\Dockerfile" -t "${acrImageName}:${acrImageTag}" $repoPath + Write-Success "Docker image built" + + Write-Step "Pushing image to ACR..." + docker tag "${acrImageName}:${acrImageTag}" "${acrLoginServer}/${acrImageName}:${acrImageTag}" + az acr login --name $acrName | Out-Null + docker push "${acrLoginServer}/${acrImageName}:${acrImageTag}" + Write-Success "Image pushed to ACR" + + # Create SQL Server + Write-Step "Creating SQL Server..." + az sql server create ` + --name $sqlServerName ` + --resource-group $ResourceGroup ` + --location $Location ` + --admin-user $sqlAdminUser ` + --admin-password $sqlAdminPasswordPlain | Out-Null + Write-Success "SQL Server created: $sqlServerName" + + # Create SQL Database + Write-Step "Creating SQL Database..." + az sql db create ` + --resource-group $ResourceGroup ` + --server $sqlServerName ` + --name $sqlDbName ` + --service-objective $SqlServiceTier | Out-Null + Write-Success "SQL Database created: $sqlDbName" + + # Configure firewall rules + Write-Step "Configuring SQL Server firewall rules..." + + # Allow Azure services (correct range for Azure services) + az sql server firewall-rule create ` + --resource-group $ResourceGroup ` + --server $sqlServerName ` + --name "AllowAzureServices" ` + --start-ip-address 0.0.0.0 ` + --end-ip-address 0.0.0.0 | Out-Null + + # Allow client IP + $myIp = (Invoke-WebRequest -Uri "https://api.ipify.org" -UseBasicParsing).Content.Trim() + az sql server firewall-rule create ` + --resource-group $ResourceGroup ` + --server $sqlServerName ` + --name "ClientIP" ` + --start-ip-address $myIp ` + --end-ip-address $myIp | Out-Null + + Write-Success "Firewall rules configured (Azure services + your IP: $myIp)" + + # Load test data + Write-Step "Loading test data into database..." + $sqlServer = "$sqlServerName.database.windows.net" + + # Wait a moment for SQL server to be fully ready + Start-Sleep -Seconds 10 + + try { + sqlcmd -S $sqlServer -U $sqlAdminUser -P $sqlAdminPasswordPlain -d $sqlDbName -i $sqlScriptPath -b + Write-Success "Test data loaded successfully" + } + catch { + Write-Warning "Failed to load test data. You may need to run it manually." + Write-Host "Command: sqlcmd -S $sqlServer -U $sqlAdminUser -P *** -d $sqlDbName -i $sqlScriptPath" -ForegroundColor Yellow + } + + # Generate DAB config + Write-Step "Generating DAB configuration..." + $dabConfigPath = Join-Path $env:TEMP "dab-config.json" + New-DabConfigFile -SqlServer $sqlServer -SqlDatabase $sqlDbName -SqlUser $sqlAdminUser -SqlPassword $sqlAdminPasswordPlain -OutputPath $dabConfigPath + + # Create connection string + $connectionString = "Server=$sqlServer;Database=$sqlDbName;User ID=$sqlAdminUser;Password=$sqlAdminPasswordPlain;TrustServerCertificate=true;" + + # Deploy Container Instance + Write-Step "Deploying Container Instance..." + az container create ` + --resource-group $ResourceGroup ` + --name $aciName ` + --image "${acrLoginServer}/${acrImageName}:${acrImageTag}" ` + --cpu $ContainerCpu ` + --memory $ContainerMemory ` + --registry-login-server $acrLoginServer ` + --registry-username $acrName ` + --registry-password $acrPassword ` + --dns-name-label $dnsLabel ` + --ports $ContainerPort ` + --environment-variables "ASPNETCORE_URLS=http://+:$ContainerPort" "DATABASE_CONNECTION_STRING=$connectionString" ` + --secure-environment-variables "DATABASE_CONNECTION_STRING=$connectionString" ` + --os-type Linux | Out-Null + + Write-Success "Container Instance deployed" + + # Get container FQDN + $containerFqdn = az container show ` + --resource-group $ResourceGroup ` + --name $aciName ` + --query "ipAddress.fqdn" -o tsv + + # Display summary + Write-Host "`n" -ForegroundColor Green + Write-Host "==========================================================================" -ForegroundColor Green + Write-Host " DEPLOYMENT SUCCESSFUL!" -ForegroundColor Green + Write-Host "==========================================================================" -ForegroundColor Green + Write-Host "" + + Write-Host "Resource Details:" -ForegroundColor Cyan + Write-Host " Resource Group: $ResourceGroup" + Write-Host " Location: $Location" + Write-Host "" + Write-Host "DAB Endpoints:" -ForegroundColor Cyan + Write-Host " App URL: http://${containerFqdn}:$ContainerPort" + Write-Host " REST API: http://${containerFqdn}:$ContainerPort/api" + Write-Host " GraphQL: http://${containerFqdn}:$ContainerPort/graphql" + Write-Host " Health Check: http://${containerFqdn}:$ContainerPort/health" + Write-Host "" + Write-Host "Database Connection:" -ForegroundColor Cyan + Write-Host " Server: $sqlServer" + Write-Host " Database: $sqlDbName" + Write-Host " Admin User: $sqlAdminUser" + Write-Host " Admin Password: $sqlAdminPasswordPlain" + Write-Host "" + Write-Host "Container Resources:" -ForegroundColor Cyan + Write-Host " CPU: $ContainerCpu cores" + Write-Host " Memory: $ContainerMemory GB" + Write-Host "" + Write-Host "Try these commands:" -ForegroundColor Cyan + Write-Host " # List all publishers" + Write-Host " curl http://${containerFqdn}:$ContainerPort/api/Publisher" + Write-Host "" + Write-Host " # GraphQL query" + Write-Host " curl http://${containerFqdn}:$ContainerPort/graphql -H 'Content-Type: application/json' -d '{\"query\":\"{publishers{items{id name}}}\"}' + Write-Host "" + Write-Host " # View container logs" + Write-Host " az container logs --resource-group $ResourceGroup --name $aciName" + Write-Host "" + Write-Host "Configuration file generated at: $dabConfigPath" -ForegroundColor Yellow + Write-Host "" + Write-Host "To delete all resources, run:" -ForegroundColor Yellow + Write-Host " az group delete --name $ResourceGroup --yes --no-wait" + Write-Host "" + Write-Host "NOTE: Container Instances use HTTP (not HTTPS). For production, consider using Azure Container Apps or App Service." -ForegroundColor Yellow + Write-Host "" + +} +catch { + Write-Error "Deployment failed: $_" + Write-Host $_.Exception.Message -ForegroundColor Red + Write-Host $_.ScriptStackTrace -ForegroundColor Red + + if (-not $SkipCleanup) { + $cleanup = Read-Host "`nDelete created resources? (y/N)" + if ($cleanup -eq 'y') { + Write-Step "Cleaning up resources..." + az group delete --name $ResourceGroup --yes --no-wait + Write-Host "Cleanup initiated (running in background)" -ForegroundColor Yellow + } + } + + exit 1 +} \ No newline at end of file diff --git a/samples/azure/readme.md b/samples/azure/readme.md index 012fbcc6a0..915f06bb6e 100644 --- a/samples/azure/readme.md +++ b/samples/azure/readme.md @@ -1,7 +1,237 @@ -# Azure Deploy Sample +# Azure Deployment Samples for Data API Builder -Use the provided `azure-deploy.sh` script to deploy Data API builder in an Azure Container Instance as described in [Running Data API Builder in Azure](https://learn.microsoft.com/azure/data-api-builder/running-in-azure) +This directory contains scripts and samples for deploying Data API builder (DAB) to various Azure services. -Use the provided `azure-container-apps-deploy.sh` script to deploy Data API builder in an Azure Container Apps as described in [Running Data API Builder in Azure](https://learn.microsoft.com/azure/data-api-builder/running-in-azure) +## Quick Start Scripts (PowerShell) -Make sure to have a valid `dab-config.json` file in the same directory as the script. \ No newline at end of file +### 🚀 New: Automated Starter Scripts + +We provide two comprehensive PowerShell starter scripts that create a complete DAB environment from scratch, including infrastructure, database, and test data: + +#### **[azure-container-apps-dab-starter.ps1](./azure-container-apps-dab-starter.ps1)** ⭐ Recommended +Deploys DAB to **Azure Container Apps** with auto-scaling, HTTPS, and health monitoring. + +```powershell +# Quick start with auto-generated names +.\azure-container-apps-dab-starter.ps1 + +# Custom deployment +.\azure-container-apps-dab-starter.ps1 -ResourcePrefix "mydab" -Location "westus2" +``` + +**Features:** +- ✅ Auto-scaling (configurable min/max replicas) +- ✅ HTTPS enabled by default +- ✅ Built-in health probes +- ✅ Managed environment +- ✅ Better for production workloads + +#### **[azure-container-instances-dab-starter.ps1](./azure-container-instances-dab-starter.ps1)** +Deploys DAB to **Azure Container Instances** for simpler, single-instance deployments. + +```powershell +# Quick start +.\azure-container-instances-dab-starter.ps1 + +# With custom resources +.\azure-container-instances-dab-starter.ps1 -ContainerCpu 2 -ContainerMemory 3 +``` + +**Features:** +- ✅ Simpler architecture +- ✅ Faster startup +- ✅ Lower cost for testing +- ✅ Good for development/testing + +### What These Scripts Do + +Both starter scripts automatically: + +1. **Validate Prerequisites** - Check for Azure CLI, Docker, sqlcmd +2. **Create Azure Resources**: + - Resource Group + - Azure Container Registry (ACR) + - MS SQL Server & Database + - Container deployment (Apps or Instances) +3. **Build & Deploy** - Build DAB Docker image and push to ACR +4. **Configure Security** - Set up SQL firewall rules (Azure services + your IP) +5. **Load Test Data** - Import sample database schema and data from `src/Service.Tests/DatabaseSchema-MsSql.sql` +6. **Generate Config** - Create a working DAB configuration file +7. **Provide Endpoints** - Display all connection details and example commands + +### Prerequisites + +Before running these scripts, ensure you have: + +- **Azure CLI** - [Install](https://aka.ms/InstallAzureCLIDirect) +- **Docker Desktop** - [Install](https://www.docker.com/products/docker-desktop) (must be running) +- **SQL Server Command Line Tools (sqlcmd)** - [Install](https://aka.ms/sqlcmd) or run: `winget install Microsoft.SqlServer.SqlCmd` +- **PowerShell 5.1 or higher** (included with Windows) +- **Azure Subscription** with sufficient permissions + +### Quick Setup + +```powershell +# 1. Login to Azure +az login + +# 2. Clone the repository (if not already done) +git clone https://github.com/Azure/data-api-builder.git +cd data-api-builder/samples/azure + +# 3. Run the script +.\azure-container-apps-dab-starter.ps1 + +# The script will: +# - Auto-generate resource names +# - Show a deployment summary +# - Ask for confirmation before proceeding +# - Display connection details after deployment +``` + +### Script Parameters + +Both scripts support extensive customization: + +```powershell +# Common Parameters +-SubscriptionId # Azure subscription (uses current if not specified) +-ResourceGroup # Resource group name (auto-generated if not provided) +-Location # Azure region (default: eastus) +-ResourcePrefix # Prefix for all resource names (auto-generated if not provided) +-SqlAdminPassword # SQL admin password (auto-generated if not provided) +-ContainerPort # DAB container port (default: 5000) +-SqlServiceTier # SQL DB tier: Basic, S0, S1, S2, P1, P2 (default: S0) +-SkipCleanup # Keep resources even if deployment fails + +# Container Apps Specific +-MinReplicas # Minimum replicas (default: 1) +-MaxReplicas # Maximum replicas (default: 3) + +# Container Instances Specific +-ContainerCpu # CPU cores: 1-4 (default: 1) +-ContainerMemory # Memory in GB: 0.5-16 (default: 1.5) +``` + +### Examples + +```powershell +# Minimal - uses all defaults with auto-generation +.\azure-container-apps-dab-starter.ps1 + +# Custom prefix and location +.\azure-container-apps-dab-starter.ps1 -ResourcePrefix "mydab" -Location "westus2" + +# Specific subscription and resource group +.\azure-container-apps-dab-starter.ps1 -SubscriptionId "xxx-xxx" -ResourceGroup "my-rg" + +# Production configuration with scaling +.\azure-container-apps-dab-starter.ps1 -SqlServiceTier "S2" -MinReplicas 2 -MaxReplicas 10 + +# High-performance Container Instance +.\azure-container-instances-dab-starter.ps1 -ContainerCpu 4 -ContainerMemory 8 + +# Keep resources on failure for debugging +.\azure-container-apps-dab-starter.ps1 -SkipCleanup +``` + +### After Deployment + +Once deployed, you'll receive a summary with: + +- **App URLs**: REST API, GraphQL, and health check endpoints +- **Database credentials**: Server, database, username, password +- **Sample commands**: Ready-to-use curl commands to test your API +- **Cleanup command**: How to delete all resources + +Example output: +``` +DAB Endpoints: + App URL: https://dab-aca-abc123.eastus.azurecontainerapps.io + REST API: https://dab-aca-abc123.eastus.azurecontainerapps.io/api + GraphQL: https://dab-aca-abc123.eastus.azurecontainerapps.io/graphql + Health Check: https://dab-aca-abc123.eastus.azurecontainerapps.io/health + +Try these commands: + # List all publishers + curl https://dab-aca-abc123.eastus.azurecontainerapps.io/api/Publisher + + # GraphQL query + curl https://dab-aca-abc123.eastus.azurecontainerapps.io/graphql \ + -H 'Content-Type: application/json' \ + -d '{"query":"{publishers{items{id name}}}"}' +``` + +### Cleanup + +To delete all resources created by the scripts: + +```powershell +# Use the cleanup command provided in the deployment output +az group delete --name --yes --no-wait +``` + +### Troubleshooting + +**Docker not running:** +``` +✗ Docker is not running. Please start Docker Desktop. +``` +→ Start Docker Desktop and wait for it to be fully running. + +**sqlcmd not found:** +``` +✗ sqlcmd is not installed. +``` +→ Install: `winget install Microsoft.SqlServer.SqlCmd` or download from [aka.ms/sqlcmd](https://aka.ms/sqlcmd) + +**Azure login required:** +``` +✗ Not logged into Azure. Please run: az login +``` +→ Run `az login` and follow the authentication prompts. + +**Test data loading failed:** +The script will continue even if test data loading fails. You can manually load it: +```powershell +sqlcmd -S .database.windows.net -U sqladmin -P -d dabdb -i src\Service.Tests\DatabaseSchema-MsSql.sql +``` + +## Manual Deployment Scripts (Bash) + +For manual deployments with existing configurations: + +### [azure-deploy.sh](./azure-deploy.sh) +Deploy Data API builder to Azure Container Instance as described in [Running Data API Builder in Azure](https://learn.microsoft.com/azure/data-api-builder/running-in-azure) + +### [azure-container-apps-deploy.sh](./azure-container-apps-deploy.sh) +Deploy Data API builder to Azure Container Apps as described in [Running Data API Builder in Azure](https://learn.microsoft.com/azure/data-api-builder/running-in-azure) + +**Note:** These scripts require a valid `dab-config.json` file in the same directory. + +## Comparison: Container Apps vs Container Instances + +| Feature | Container Apps | Container Instances | +|---------|---------------|---------------------| +| **Auto-scaling** | ✅ Yes (0-30 replicas) | ❌ No (single instance) | +| **HTTPS** | ✅ Automatic | ⚠️ Manual setup required | +| **Load Balancing** | ✅ Built-in | ❌ Single instance | +| **Health Probes** | ✅ Yes | ⚠️ Limited | +| **Managed Identity** | ✅ Yes | ⚠️ Limited | +| **Zero-downtime Deployments** | ✅ Yes | ❌ No | +| **Cost (idle)** | 💰 Can scale to 0 | 💰 Always running | +| **Best For** | Production workloads | Dev/test, simple workloads | +| **Startup Time** | ~1-2 min | ~30 sec | + +**Recommendation:** Use **Container Apps** for production workloads and **Container Instances** for development/testing. + +## Additional Resources + +- [Data API Builder Documentation](https://learn.microsoft.com/azure/data-api-builder/) +- [Running DAB in Azure](https://learn.microsoft.com/azure/data-api-builder/running-in-azure) +- [Azure Container Apps Documentation](https://learn.microsoft.com/azure/container-apps/) +- [Azure Container Instances Documentation](https://learn.microsoft.com/azure/container-instances/) + +## Contributing + +If you encounter issues or have suggestions for improving these scripts, please [open an issue](https://github.com/Azure/data-api-builder/issues) or submit a pull request. \ No newline at end of file From 2ba00b827b2f43df0ed61755c004523eb69690a6 Mon Sep 17 00:00:00 2001 From: souvikghosh04 Date: Wed, 17 Dec 2025 16:44:28 +0530 Subject: [PATCH 2/7] Enhance DAB deployment scripts with mandatory parameters and configuration file handling --- .../azure-container-apps-dab-starter.ps1 | 336 ++++++++++----- .../azure-container-instances-dab-starter.ps1 | 399 +++++++++++++----- 2 files changed, 518 insertions(+), 217 deletions(-) diff --git a/samples/azure/azure-container-apps-dab-starter.ps1 b/samples/azure/azure-container-apps-dab-starter.ps1 index dedf5af219..2e8843e5d4 100644 --- a/samples/azure/azure-container-apps-dab-starter.ps1 +++ b/samples/azure/azure-container-apps-dab-starter.ps1 @@ -42,7 +42,7 @@ Minimum number of container replicas. Default: 1 .PARAMETER MaxReplicas - Maximum number of container replicas. Default: 3 + Maximum number of container replicas. Default: 3`n`n.PARAMETER DabConfigFile`n Path to DAB configuration file. Default: src/Service.Tests/dab-config.MsSql.json .EXAMPLE .\azure-container-apps-dab-starter.ps1 @@ -67,20 +67,20 @@ [CmdletBinding()] param( - [Parameter(Mandatory=$false)] + [Parameter(Mandatory=$true)] [string]$SubscriptionId, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory=$true)] [string]$ResourceGroup, [Parameter(Mandatory=$false)] [ValidateSet('eastus', 'eastus2', 'westus', 'westus2', 'westus3', 'centralus', 'northeurope', 'westeurope', 'uksouth', 'southeastasia')] [string]$Location = "eastus", - [Parameter(Mandatory=$false)] + [Parameter(Mandatory=$true)] [string]$ResourcePrefix, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory=$true)] [SecureString]$SqlAdminPassword, [Parameter(Mandatory=$false)] @@ -99,12 +99,21 @@ param( [Parameter(Mandatory=$false)] [ValidateRange(1, 30)] - [int]$MaxReplicas = 3 + [int]$MaxReplicas = 3, + + [Parameter(Mandatory=$false)] + [string]$DabConfigFile ) $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" +# Validate replica configuration +if ($MaxReplicas -lt $MinReplicas) { + Write-Host "[ERROR] MaxReplicas ($MaxReplicas) must be greater than or equal to MinReplicas ($MinReplicas)" -ForegroundColor Red + exit 1 +} + # ============================================================================ # HELPER FUNCTIONS # ============================================================================ @@ -119,7 +128,7 @@ function Write-Success { Write-Host "[OK] $Message" -ForegroundColor Green } -function Write-Error { +function Write-ErrorMessage { param([string]$Message) Write-Host "[ERROR] $Message" -ForegroundColor Red } @@ -129,24 +138,40 @@ function Test-Prerequisites { # Check Azure CLI if (-not (Get-Command az -ErrorAction SilentlyContinue)) { - Write-Error "Azure CLI is not installed. Please install from: https://aka.ms/InstallAzureCLIDirect" + Write-ErrorMessage "Azure CLI is not installed. Please install from: https://aka.ms/InstallAzureCLIDirect" exit 1 } Write-Success "Azure CLI installed" # Check Docker if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { - Write-Error "Docker is not installed. Please install Docker Desktop from: https://www.docker.com/products/docker-desktop" + Write-ErrorMessage "Docker is not installed. Please install Docker Desktop from: https://www.docker.com/products/docker-desktop" + exit 1 + } + + # Check Azure CLI containerapp extension (ignore metadata permission errors) + $extensionCheck = az extension list --query "[?name=='containerapp'].name" -o tsv 2>$null + if ([string]::IsNullOrEmpty($extensionCheck)) { + Write-ErrorMessage "Azure CLI containerapp extension is not installed." + Write-Host "Please run: az extension add --name containerapp --upgrade --yes" -ForegroundColor Yellow + Write-Host "If permission errors occur, run PowerShell as Administrator." -ForegroundColor Yellow exit 1 } + Write-Success "Azure CLI containerapp extension installed" # Check if Docker is running try { - docker ps | Out-Null + $dockerOutput = docker ps 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-ErrorMessage "Docker is not running. Please start Docker Desktop." + Write-Host "Error: $dockerOutput" -ForegroundColor Red + exit 1 + } Write-Success "Docker is running" } catch { - Write-Error "Docker is not running. Please start Docker Desktop." + Write-ErrorMessage "Docker is not running or not responding. Please start Docker Desktop." + Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red exit 1 } @@ -162,7 +187,7 @@ function Test-Prerequisites { # Check if logged into Azure $account = az account show 2>$null | ConvertFrom-Json if (-not $account) { - Write-Error "Not logged into Azure. Please run: az login" + Write-ErrorMessage "Not logged into Azure. Please run: az login" exit 1 } Write-Success "Logged into Azure as $($account.user.name)" @@ -175,83 +200,42 @@ function Get-UniqueResourceName { } function New-RandomPassword { - $length = 16 + param( + [int]$Length = 16 + ) $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()' - $password = -join ((1..$length) | ForEach-Object { $chars[(Get-Random -Maximum $chars.Length)] }) + $password = -join ((1..$Length) | ForEach-Object { $chars[(Get-Random -Maximum $chars.Length)] }) # Ensure complexity requirements $password = 'A1!' + $password return $password } -function New-DabConfigFile { +function Update-DabConfigFile { param( - [string]$SqlServer, - [string]$SqlDatabase, - [string]$SqlUser, - [string]$SqlPassword, + [string]$SourceConfigPath, + [string]$ConnectionString, [string]$OutputPath ) - # Build config as JSON string directly to avoid PowerShell hashtable issues - $configJson = @' -{ - "$schema": "https://github.com/Azure/data-api-builder/releases/latest/download/dab.draft.schema.json", - "data-source": { - "database-type": "mssql", - "connection-string": "@env('DATABASE_CONNECTION_STRING')" - }, - "runtime": { - "rest": { - "enabled": true, - "path": "/api" - }, - "graphql": { - "enabled": true, - "path": "/graphql", - "allow-introspection": true - }, - "host": { - "mode": "production", - "cors": { - "origins": ["*"], - "allow-credentials": false - } - } - }, - "entities": { - "Book": { - "source": "books", - "permissions": [ - { - "role": "anonymous", - "actions": ["create", "read", "update", "delete"] - } - ] - }, - "Publisher": { - "source": "publishers", - "permissions": [ - { - "role": "anonymous", - "actions": ["create", "read", "update", "delete"] - } - ] - }, - "Author": { - "source": "authors", - "permissions": [ - { - "role": "anonymous", - "actions": ["create", "read", "update", "delete"] - } - ] + # Read the source config file + $configContent = Get-Content -Path $SourceConfigPath -Raw + + # Parse JSON + $config = $configContent | ConvertFrom-Json + + # Update the connection string + $config.'data-source'.'connection-string' = $ConnectionString + + # Ensure production mode for Container Apps + if ($config.runtime.host.mode -eq "development") { + $config.runtime.host.mode = "production" } - } -} -'@ - $configJson | Set-Content -Path $OutputPath - Write-Success "Generated DAB config file: $OutputPath" + # Convert back to JSON and save + $configContent = $config | ConvertTo-Json -Depth 100 + $configContent | Set-Content -Path $OutputPath -Encoding UTF8 + + Write-Success "Updated DAB config file: $OutputPath" } # ============================================================================ @@ -274,17 +258,28 @@ while ($repoPath -and -not (Test-Path (Join-Path $repoPath "Dockerfile"))) { } if (-not $repoPath -or -not (Test-Path (Join-Path $repoPath "Dockerfile"))) { - Write-Error "Could not find data-api-builder repository root (looking for Dockerfile)" + Write-ErrorMessage "Could not find data-api-builder repository root (looking for Dockerfile)" exit 1 } $sqlScriptPath = Join-Path $repoPath "src\Service.Tests\DatabaseSchema-MsSql.sql" if (-not (Test-Path $sqlScriptPath)) { - Write-Error "Could not find SQL script at: $sqlScriptPath" + Write-ErrorMessage "Could not find SQL script at: $sqlScriptPath" + exit 1 +} + +# Set default DAB config file path if not provided +if (-not $DabConfigFile) { + $DabConfigFile = Join-Path $repoPath "src\Service.Tests\dab-config.MsSql.json" +} + +if (-not (Test-Path $DabConfigFile)) { + Write-ErrorMessage "DAB config file not found at: $DabConfigFile" exit 1 } Write-Success "Repository root: $repoPath" +Write-Success "Using DAB config: $DabConfigFile" # Generate or validate parameters if (-not $ResourcePrefix) { @@ -324,12 +319,35 @@ $acrImageTag = "latest" if (-not $SqlAdminPassword) { $generatedPassword = New-RandomPassword $SqlAdminPassword = ConvertTo-SecureString -String $generatedPassword -AsPlainText -Force - Write-Host "Generated SQL Server admin password (save this): $generatedPassword" -ForegroundColor Yellow + + # Store password to file for secure retrieval + $passwordFile = Join-Path $env:TEMP "dab-sql-password-$(Get-Date -Format 'yyyyMMdd-HHmmss').txt" + $generatedPassword | Out-File -FilePath $passwordFile -NoNewline + + # Restrict file permissions to current user only (requires elevated privileges) + try { + $acl = Get-Acl $passwordFile + $acl.SetAccessRuleProtection($true, $false) + $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($env:USERNAME, "FullControl", "Allow") + $acl.SetAccessRule($accessRule) + Set-Acl $passwordFile $acl + } + catch { + Write-Warning "Could not restrict password file ACL permissions (requires admin rights)." + } + + Write-Warning "Auto-generated SQL password has been saved to: $passwordFile" + Write-Host "Please store this password securely and delete the file after saving it to your password manager." -ForegroundColor Yellow } -$sqlAdminPasswordPlain = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( - [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SqlAdminPassword) -) +# Convert SecureString to plain text with proper memory management +$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SqlAdminPassword) +try { + $sqlAdminPasswordPlain = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr) +} +finally { + [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) +} # Summary Write-Host "`nDeployment Configuration:" -ForegroundColor Cyan @@ -425,22 +443,131 @@ try { Write-Step "Loading test data into database..." $sqlServer = "$sqlServerName.database.windows.net" - # Wait a moment for SQL server to be fully ready - Start-Sleep -Seconds 10 + # Wait for firewall rules to propagate (Azure can take up to 5 minutes) + Write-Host "Waiting for firewall rules to propagate..." -ForegroundColor Yellow + Start-Sleep -Seconds 30 - try { - sqlcmd -S $sqlServer -U $sqlAdminUser -P $sqlAdminPasswordPlain -d $sqlDbName -i $sqlScriptPath -b - Write-Success "Test data loaded successfully" + $maxRetries = 3 + $retryCount = 0 + $dataLoaded = $false + + while (-not $dataLoaded -and $retryCount -lt $maxRetries) { + try { + if ($retryCount -gt 0) { + Write-Host "Retry attempt $retryCount of $maxRetries..." -ForegroundColor Yellow + } + +$output = sqlcmd -S $sqlServer -U $sqlAdminUser -P $sqlAdminPasswordPlain -d $sqlDbName -i $sqlScriptPath -I -b 2>&1 + + if ($LASTEXITCODE -eq 0) { + $dataLoaded = $true + Write-Success "Test data loaded successfully" + } + else { + throw "sqlcmd returned exit code $LASTEXITCODE" + } + } + catch { + $errorMessage = $_.Exception.Message + if ($output) { $errorMessage = $output | Out-String } + + # Check if error is due to IP not allowed + if ($errorMessage -match "Client with IP address '([0-9.]+)' is not allowed") { + $blockedIp = $matches[1] + Write-Warning "Connection blocked from IP: $blockedIp (detected IP was: $myIp)" + Write-Host "Adding blocked IP to firewall rules..." -ForegroundColor Yellow + + try { + az sql server firewall-rule create ` + --resource-group $ResourceGroup ` + --server $sqlServerName ` + --name "ClientIP-Actual" ` + --start-ip-address $blockedIp ` + --end-ip-address $blockedIp | Out-Null + + Write-Host "Waiting for new firewall rule to propagate..." -ForegroundColor Yellow + Start-Sleep -Seconds 30 + } + catch { + Write-Warning "Failed to add blocked IP to firewall: $_" + } + } + elseif ($errorMessage -match "It may take up to five minutes") { + Write-Host "Firewall rules still propagating. Waiting 60 seconds..." -ForegroundColor Yellow + Start-Sleep -Seconds 60 + } + else { + Write-Warning "SQL connection error: $errorMessage" + if ($retryCount -lt $maxRetries - 1) { + Write-Host "Waiting 30 seconds before retry..." -ForegroundColor Yellow + Start-Sleep -Seconds 30 + } + } + + $retryCount++ + } } - catch { - Write-Warning "Failed to load test data. You may need to run it manually." - Write-Host "Command: sqlcmd -S $sqlServer -U $sqlAdminUser -P *** -d $sqlDbName -i $sqlScriptPath" -ForegroundColor Yellow + + if (-not $dataLoaded) { + Write-Warning "Failed to load test data after $maxRetries attempts. You may need to run it manually." + Write-Host "Command: sqlcmd -S $sqlServer -U $sqlAdminUser -P *** -d $sqlDbName -i $sqlScriptPath -I" -ForegroundColor Yellow } - # Generate DAB config - Write-Step "Generating DAB configuration..." + # Prepare DAB config with actual connection string (same as ACI) + Write-Step "Preparing DAB configuration..." $dabConfigPath = Join-Path $env:TEMP "dab-config.json" - New-DabConfigFile -SqlServer $sqlServer -SqlDatabase $sqlDbName -SqlUser $sqlAdminUser -SqlPassword $sqlAdminPasswordPlain -OutputPath $dabConfigPath + $connectionString = "Server=$sqlServer,1433;Persist Security Info=False;User ID=$sqlAdminUser;Password=$sqlAdminPasswordPlain;Initial Catalog=$sqlDbName;MultipleActiveResultSets=False;Connection Timeout=30;TrustServerCertificate=True;" + + Update-DabConfigFile -SourceConfigPath $DabConfigFile -ConnectionString $connectionString -OutputPath $dabConfigPath + + # Upload config to Azure Storage Account for container to download (same as ACI) + Write-Step "Creating storage account for config file..." + $storageAccountName = "dabstorage$(Get-Random -Minimum 10000 -Maximum 99999)" + + az storage account create ` + --name $storageAccountName ` + --resource-group $ResourceGroup ` + --location $Location ` + --sku Standard_LRS ` + --kind StorageV2 | Out-Null + + Write-Success "Storage account created: $storageAccountName" + + # Get storage account key + $storageKey = az storage account keys list ` + --resource-group $ResourceGroup ` + --account-name $storageAccountName ` + --query "[0].value" -o tsv + + # Create container (blob container, not ACA) + az storage container create ` + --name "config" ` + --account-name $storageAccountName ` + --account-key $storageKey | Out-Null + + # Upload config file + az storage blob upload ` + --account-name $storageAccountName ` + --account-key $storageKey ` + --container-name "config" ` + --name "dab-config.json" ` + --file $dabConfigPath ` + --overwrite | Out-Null + + Write-Success "Config file uploaded to blob storage" + + # Generate SAS URL for the blob (valid for 1 year) + $expiryDate = (Get-Date).AddYears(1).ToString("yyyy-MM-ddTHH:mm:ssZ") + $configUrl = az storage blob generate-sas ` + --account-name $storageAccountName ` + --account-key $storageKey ` + --container-name "config" ` + --name "dab-config.json" ` + --permissions r ` + --expiry $expiryDate ` + --full-uri -o tsv + + Write-Success "Generated SAS URL for config download" # Create Container Apps Environment Write-Step "Creating Container Apps Environment..." @@ -450,11 +577,11 @@ try { --location $Location | Out-Null Write-Success "Container Apps Environment created" - # Create connection string - $connectionString = "Server=$sqlServer;Database=$sqlDbName;User ID=$sqlAdminUser;Password=$sqlAdminPasswordPlain;TrustServerCertificate=true;" - - # Deploy Container App + # Deploy Container App (same blob storage + curl approach as ACI) Write-Step "Deploying Container App..." + Write-Host "Note: Container will download config from blob storage on startup (same as ACI)" -ForegroundColor Yellow + + # Use secrets for CONFIG_URL to avoid command-line shell parsing of SAS URL special characters az containerapp create ` --name $acaName ` --resource-group $ResourceGroup ` @@ -470,15 +597,16 @@ try { --registry-server $acrLoginServer ` --registry-username $acrName ` --registry-password $acrPassword ` - --secrets "db-connection-string=$connectionString" ` - --env-vars "ASPNETCORE_URLS=http://+:$ContainerPort" "DATABASE_CONNECTION_STRING=secretref:db-connection-string" | Out-Null + --secrets "config-url=$configUrl" ` + --env-vars "ASPNETCORE_URLS=http://+:$ContainerPort" "CONFIG_URL=secretref:config-url" ` + --command "/bin/sh" "-c" "curl -o /App/dab-config.json \"\$CONFIG_URL\" && dotnet Azure.DataApiBuilder.Service.dll --ConfigFileName /App/dab-config.json" | Out-Null Write-Success "Container App deployed" # Get app URL $appUrl = az containerapp show ` --name $acaName ` - --resource-group $resourceGroup ` + --resource-group $ResourceGroup ` --query "properties.configuration.ingress.fqdn" -o tsv # Display summary @@ -502,7 +630,7 @@ try { Write-Host " Server: $sqlServer" Write-Host " Database: $sqlDbName" Write-Host " Admin User: $sqlAdminUser" - Write-Host " Admin Password: $sqlAdminPasswordPlain" + Write-Host " Admin Password: ********** (saved to file earlier)" Write-Host "" Write-Host "Try these commands:" -ForegroundColor Cyan Write-Host " # List all publishers" @@ -520,7 +648,7 @@ try { } catch { - Write-Error "Deployment failed: $_" + Write-ErrorMessage "Deployment failed: $_" Write-Host $_.Exception.Message -ForegroundColor Red Write-Host $_.ScriptStackTrace -ForegroundColor Red @@ -534,4 +662,4 @@ catch { } exit 1 -} \ No newline at end of file +} diff --git a/samples/azure/azure-container-instances-dab-starter.ps1 b/samples/azure/azure-container-instances-dab-starter.ps1 index a855e39528..cce591ac41 100644 --- a/samples/azure/azure-container-instances-dab-starter.ps1 +++ b/samples/azure/azure-container-instances-dab-starter.ps1 @@ -43,6 +43,9 @@ .PARAMETER ContainerMemory Memory in GB for the container. Default: 1.5 +.PARAMETER DabConfigFile + Path to DAB configuration file. Default: src/Service.Tests/dab-config.MsSql.json + .EXAMPLE .\azure-container-instances-dab-starter.ps1 Runs with auto-generated values and prompts for confirmation @@ -66,20 +69,20 @@ [CmdletBinding()] param( - [Parameter(Mandatory=$false)] + [Parameter(Mandatory=$true)] [string]$SubscriptionId, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory=$true)] [string]$ResourceGroup, [Parameter(Mandatory=$false)] [ValidateSet('eastus', 'eastus2', 'westus', 'westus2', 'westus3', 'centralus', 'northeurope', 'westeurope', 'uksouth', 'southeastasia')] [string]$Location = "eastus", - [Parameter(Mandatory=$false)] + [Parameter(Mandatory=$true)] [string]$ResourcePrefix, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory=$true)] [SecureString]$SqlAdminPassword, [Parameter(Mandatory=$false)] @@ -98,7 +101,10 @@ param( [Parameter(Mandatory=$false)] [ValidateRange(0.5, 16)] - [double]$ContainerMemory = 1.5 + [double]$ContainerMemory = 1.5, + + [Parameter(Mandatory=$false)] + [string]$DabConfigFile ) $ErrorActionPreference = "Stop" @@ -118,7 +124,7 @@ function Write-Success { Write-Host "[OK] $Message" -ForegroundColor Green } -function Write-Error { +function Write-ErrorMessage { param([string]$Message) Write-Host "[ERROR] $Message" -ForegroundColor Red } @@ -128,24 +134,30 @@ function Test-Prerequisites { # Check Azure CLI if (-not (Get-Command az -ErrorAction SilentlyContinue)) { - Write-Error "Azure CLI is not installed. Please install from: https://aka.ms/InstallAzureCLIDirect" + Write-ErrorMessage "Azure CLI is not installed. Please install from: https://aka.ms/InstallAzureCLIDirect" exit 1 } Write-Success "Azure CLI installed" # Check Docker if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { - Write-Error "Docker is not installed. Please install Docker Desktop from: https://www.docker.com/products/docker-desktop" + Write-ErrorMessage "Docker is not installed. Please install Docker Desktop from: https://www.docker.com/products/docker-desktop" exit 1 } # Check if Docker is running try { - docker ps | Out-Null + $dockerOutput = docker ps 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-ErrorMessage "Docker is not running. Please start Docker Desktop." + Write-Host "Error: $dockerOutput" -ForegroundColor Red + exit 1 + } Write-Success "Docker is running" } catch { - Write-Error "Docker is not running. Please start Docker Desktop." + Write-ErrorMessage "Docker is not running or not responding. Please start Docker Desktop." + Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red exit 1 } @@ -161,7 +173,7 @@ function Test-Prerequisites { # Check if logged into Azure $account = az account show 2>$null | ConvertFrom-Json if (-not $account) { - Write-Error "Not logged into Azure. Please run: az login" + Write-ErrorMessage "Not logged into Azure. Please run: az login" exit 1 } Write-Success "Logged into Azure as $($account.user.name)" @@ -174,83 +186,42 @@ function Get-UniqueResourceName { } function New-RandomPassword { - $length = 16 + param( + [int]$Length = 16 + ) $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()' - $password = -join ((1..$length) | ForEach-Object { $chars[(Get-Random -Maximum $chars.Length)] }) + $password = -join ((1..$Length) | ForEach-Object { $chars[(Get-Random -Maximum $chars.Length)] }) # Ensure complexity requirements $password = 'A1!' + $password return $password } -function New-DabConfigFile { +function Update-DabConfigFile { param( - [string]$SqlServer, - [string]$SqlDatabase, - [string]$SqlUser, - [string]$SqlPassword, + [string]$SourceConfigPath, + [string]$ConnectionString, [string]$OutputPath ) - # Build config as JSON string directly to avoid PowerShell hashtable issues - $configJson = @' -{ - "$schema": "https://github.com/Azure/data-api-builder/releases/latest/download/dab.draft.schema.json", - "data-source": { - "database-type": "mssql", - "connection-string": "@env('DATABASE_CONNECTION_STRING')" - }, - "runtime": { - "rest": { - "enabled": true, - "path": "/api" - }, - "graphql": { - "enabled": true, - "path": "/graphql", - "allow-introspection": true - }, - "host": { - "mode": "production", - "cors": { - "origins": ["*"], - "allow-credentials": false - } - } - }, - "entities": { - "Book": { - "source": "books", - "permissions": [ - { - "role": "anonymous", - "actions": ["create", "read", "update", "delete"] - } - ] - }, - "Publisher": { - "source": "publishers", - "permissions": [ - { - "role": "anonymous", - "actions": ["create", "read", "update", "delete"] - } - ] - }, - "Author": { - "source": "authors", - "permissions": [ - { - "role": "anonymous", - "actions": ["create", "read", "update", "delete"] - } - ] + # Read the source config file + $configContent = Get-Content -Path $SourceConfigPath -Raw + + # Parse JSON + $config = $configContent | ConvertFrom-Json + + # Update the connection string + $config.'data-source'.'connection-string' = $ConnectionString + + # Ensure production mode for Container Instances + if ($config.runtime.host.mode -eq "development") { + $config.runtime.host.mode = "production" } - } -} -'@ - $configJson | Set-Content -Path $OutputPath - Write-Success "Generated DAB config file: $OutputPath" + # Convert back to JSON and save + $configContent = $config | ConvertTo-Json -Depth 100 + $configContent | Set-Content -Path $OutputPath -Encoding UTF8 + + Write-Success "Updated DAB config file: $OutputPath" } # ============================================================================ @@ -273,17 +244,28 @@ while ($repoPath -and -not (Test-Path (Join-Path $repoPath "Dockerfile"))) { } if (-not $repoPath -or -not (Test-Path (Join-Path $repoPath "Dockerfile"))) { - Write-Error "Could not find data-api-builder repository root (looking for Dockerfile)" + Write-ErrorMessage "Could not find data-api-builder repository root (looking for Dockerfile)" exit 1 } $sqlScriptPath = Join-Path $repoPath "src\Service.Tests\DatabaseSchema-MsSql.sql" if (-not (Test-Path $sqlScriptPath)) { - Write-Error "Could not find SQL script at: $sqlScriptPath" + Write-ErrorMessage "Could not find SQL script at: $sqlScriptPath" + exit 1 +} + +# Set default DAB config file path if not provided +if (-not $DabConfigFile) { + $DabConfigFile = Join-Path $repoPath "src\Service.Tests\dab-config.MsSql.json" +} + +if (-not (Test-Path $DabConfigFile)) { + Write-ErrorMessage "DAB config file not found at: $DabConfigFile" exit 1 } Write-Success "Repository root: $repoPath" +Write-Success "Using DAB config: $DabConfigFile" # Generate or validate parameters if (-not $ResourcePrefix) { @@ -324,12 +306,35 @@ $acrImageTag = "latest" if (-not $SqlAdminPassword) { $generatedPassword = New-RandomPassword $SqlAdminPassword = ConvertTo-SecureString -String $generatedPassword -AsPlainText -Force - Write-Host "Generated SQL Server admin password (save this): $generatedPassword" -ForegroundColor Yellow + + # Store password to file for secure retrieval + $passwordFile = Join-Path $env:TEMP "dab-sql-password-$(Get-Date -Format 'yyyyMMdd-HHmmss').txt" + $generatedPassword | Out-File -FilePath $passwordFile -NoNewline + + # Restrict file permissions to current user only (requires elevated privileges) + try { + $acl = Get-Acl $passwordFile + $acl.SetAccessRuleProtection($true, $false) + $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($env:USERNAME, "FullControl", "Allow") + $acl.SetAccessRule($accessRule) + Set-Acl $passwordFile $acl + } + catch { + Write-Warning "Could not restrict password file ACL permissions (requires admin rights)." + } + + Write-Warning "Auto-generated SQL password has been saved to: $passwordFile" + Write-Host "Please store this password securely and delete the file after saving it to your password manager." -ForegroundColor Yellow } -$sqlAdminPasswordPlain = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( - [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SqlAdminPassword) -) +# Convert SecureString to plain text with proper memory management +$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SqlAdminPassword) +try { + $sqlAdminPasswordPlain = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr) +} +finally { + [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) +} # Summary Write-Host "`nDeployment Configuration:" -ForegroundColor Cyan @@ -426,45 +431,213 @@ try { Write-Step "Loading test data into database..." $sqlServer = "$sqlServerName.database.windows.net" - # Wait a moment for SQL server to be fully ready - Start-Sleep -Seconds 10 + # Wait for firewall rules to propagate (Azure can take up to 5 minutes) + Write-Host "Waiting for firewall rules to propagate..." -ForegroundColor Yellow + Start-Sleep -Seconds 30 - try { - sqlcmd -S $sqlServer -U $sqlAdminUser -P $sqlAdminPasswordPlain -d $sqlDbName -i $sqlScriptPath -b - Write-Success "Test data loaded successfully" + $maxRetries = 3 + $retryCount = 0 + $dataLoaded = $false + + while (-not $dataLoaded -and $retryCount -lt $maxRetries) { + try { + if ($retryCount -gt 0) { + Write-Host "Retry attempt $retryCount of $maxRetries..." -ForegroundColor Yellow + } + +$output = sqlcmd -S $sqlServer -U $sqlAdminUser -P $sqlAdminPasswordPlain -d $sqlDbName -i $sqlScriptPath -I -b 2>&1 + + if ($LASTEXITCODE -eq 0) { + $dataLoaded = $true + Write-Success "Test data loaded successfully" + } + else { + throw "sqlcmd returned exit code $LASTEXITCODE" + } + } + catch { + $errorMessage = $_.Exception.Message + if ($output) { $errorMessage = $output | Out-String } + + # Check if error is due to IP not allowed + if ($errorMessage -match "Client with IP address '([0-9.]+)' is not allowed") { + $blockedIp = $matches[1] + Write-Warning "Connection blocked from IP: $blockedIp (detected IP was: $myIp)" + Write-Host "Adding blocked IP to firewall rules..." -ForegroundColor Yellow + + try { + az sql server firewall-rule create ` + --resource-group $ResourceGroup ` + --server $sqlServerName ` + --name "ClientIP-Actual" ` + --start-ip-address $blockedIp ` + --end-ip-address $blockedIp | Out-Null + + Write-Host "Waiting for new firewall rule to propagate..." -ForegroundColor Yellow + Start-Sleep -Seconds 30 + } + catch { + Write-Warning "Failed to add blocked IP to firewall: $_" + } + } + elseif ($errorMessage -match "It may take up to five minutes") { + Write-Host "Firewall rules still propagating. Waiting 60 seconds..." -ForegroundColor Yellow + Start-Sleep -Seconds 60 + } + else { + Write-Warning "SQL connection error: $errorMessage" + if ($retryCount -lt $maxRetries - 1) { + Write-Host "Waiting 30 seconds before retry..." -ForegroundColor Yellow + Start-Sleep -Seconds 30 + } + } + + $retryCount++ + } } - catch { - Write-Warning "Failed to load test data. You may need to run it manually." - Write-Host "Command: sqlcmd -S $sqlServer -U $sqlAdminUser -P *** -d $sqlDbName -i $sqlScriptPath" -ForegroundColor Yellow + + if (-not $dataLoaded) { + Write-Warning "Failed to load test data after $maxRetries attempts. You may need to run it manually." + Write-Host "Command: sqlcmd -S $sqlServer -U $sqlAdminUser -P *** -d $sqlDbName -i $sqlScriptPath -I" -ForegroundColor Yellow } - # Generate DAB config - Write-Step "Generating DAB configuration..." + # Prepare DAB config with actual connection string + Write-Step "Preparing DAB configuration..." $dabConfigPath = Join-Path $env:TEMP "dab-config.json" - New-DabConfigFile -SqlServer $sqlServer -SqlDatabase $sqlDbName -SqlUser $sqlAdminUser -SqlPassword $sqlAdminPasswordPlain -OutputPath $dabConfigPath - - # Create connection string - $connectionString = "Server=$sqlServer;Database=$sqlDbName;User ID=$sqlAdminUser;Password=$sqlAdminPasswordPlain;TrustServerCertificate=true;" + $connectionString = "Server=$sqlServer,1433;Persist Security Info=False;User ID=$sqlAdminUser;Password=$sqlAdminPasswordPlain;Initial Catalog=$sqlDbName;MultipleActiveResultSets=False;Connection Timeout=30;TrustServerCertificate=True;" + + Update-DabConfigFile -SourceConfigPath $DabConfigFile -ConnectionString $connectionString -OutputPath $dabConfigPath - # Deploy Container Instance + # Upload config to Azure Storage Account for container to download + Write-Step "Creating storage account for config file..." + $storageAccountName = "dabstorage$(Get-Random -Minimum 10000 -Maximum 99999)" + + az storage account create ` + --name $storageAccountName ` + --resource-group $ResourceGroup ` + --location $Location ` + --sku Standard_LRS ` + --kind StorageV2 | Out-Null + + Write-Success "Storage account created: $storageAccountName" + + # Get storage account key + $storageKey = az storage account keys list ` + --resource-group $ResourceGroup ` + --account-name $storageAccountName ` + --query "[0].value" -o tsv + + # Create container (blob container, not ACI) + az storage container create ` + --name "config" ` + --account-name $storageAccountName ` + --account-key $storageKey | Out-Null + + # Upload config file + az storage blob upload ` + --account-name $storageAccountName ` + --account-key $storageKey ` + --container-name "config" ` + --name "dab-config.json" ` + --file $dabConfigPath ` + --overwrite | Out-Null + + Write-Success "Config file uploaded to blob storage" + + # Generate SAS URL for the blob (valid for 1 year) + $expiryDate = (Get-Date).AddYears(1).ToString("yyyy-MM-ddTHH:mm:ssZ") + $configUrl = az storage blob generate-sas ` + --account-name $storageAccountName ` + --account-key $storageKey ` + --container-name "config" ` + --name "dab-config.json" ` + --permissions r ` + --expiry $expiryDate ` + --full-uri -o tsv + + Write-Success "Generated SAS URL for config download" + + # Deploy Container Instance with command to download config Write-Step "Deploying Container Instance..." - az container create ` + Write-Host "Note: Container will download config from blob storage on startup" -ForegroundColor Yellow + + # Create ARM template to properly handle startup command with special characters + $armTemplate = @{ + '$schema' = 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion = '1.0.0.0' + resources = @( + @{ + type = 'Microsoft.ContainerInstance/containerGroups' + apiVersion = '2021-09-01' + name = $aciName + location = $Location + properties = @{ + containers = @( + @{ + name = 'dab' + properties = @{ + image = "${acrLoginServer}/${acrImageName}:${acrImageTag}" + resources = @{ + requests = @{ + cpu = $ContainerCpu + memoryInGB = $ContainerMemory + } + } + ports = @( + @{ + port = $ContainerPort + protocol = 'TCP' + } + ) + environmentVariables = @( + @{ + name = 'ASPNETCORE_URLS' + value = "http://+:$ContainerPort" + } + @{ + name = 'CONFIG_URL' + value = $configUrl + } + ) + command = @('/bin/sh', '-c', 'curl -o /App/dab-config.json "$CONFIG_URL" && dotnet Azure.DataApiBuilder.Service.dll --ConfigFileName /App/dab-config.json') + } + } + ) + imageRegistryCredentials = @( + @{ + server = $acrLoginServer + username = $acrName + password = $acrPassword + } + ) + ipAddress = @{ + type = 'Public' + dnsNameLabel = $dnsLabel + ports = @( + @{ + port = $ContainerPort + protocol = 'TCP' + } + ) + } + osType = 'Linux' + restartPolicy = 'Always' + } + } + ) + } + + $armTemplatePath = Join-Path $env:TEMP "aci-deployment-template.json" + $armTemplate | ConvertTo-Json -Depth 10 | Set-Content -Path $armTemplatePath -Encoding UTF8 + + az deployment group create ` --resource-group $ResourceGroup ` - --name $aciName ` - --image "${acrLoginServer}/${acrImageName}:${acrImageTag}" ` - --cpu $ContainerCpu ` - --memory $ContainerMemory ` - --registry-login-server $acrLoginServer ` - --registry-username $acrName ` - --registry-password $acrPassword ` - --dns-name-label $dnsLabel ` - --ports $ContainerPort ` - --environment-variables "ASPNETCORE_URLS=http://+:$ContainerPort" "DATABASE_CONNECTION_STRING=$connectionString" ` - --secure-environment-variables "DATABASE_CONNECTION_STRING=$connectionString" ` - --os-type Linux | Out-Null + --template-file $armTemplatePath | Out-Null + + Remove-Item $armTemplatePath -ErrorAction SilentlyContinue Write-Success "Container Instance deployed" - + # Get container FQDN $containerFqdn = az container show ` --resource-group $ResourceGroup ` @@ -492,7 +665,7 @@ try { Write-Host " Server: $sqlServer" Write-Host " Database: $sqlDbName" Write-Host " Admin User: $sqlAdminUser" - Write-Host " Admin Password: $sqlAdminPasswordPlain" + Write-Host " Admin Password: ********** (saved to file earlier)" Write-Host "" Write-Host "Container Resources:" -ForegroundColor Cyan Write-Host " CPU: $ContainerCpu cores" @@ -518,7 +691,7 @@ try { } catch { - Write-Error "Deployment failed: $_" + Write-ErrorMessage "Deployment failed: $_" Write-Host $_.Exception.Message -ForegroundColor Red Write-Host $_.ScriptStackTrace -ForegroundColor Red From 5db9bcad5e849c439cb827351b3f47eabdd096a8 Mon Sep 17 00:00:00 2001 From: souvikghosh04 Date: Wed, 17 Dec 2025 22:20:53 +0530 Subject: [PATCH 3/7] Enhance DAB deployment scripts with additional parameters for CPU and memory configuration, and update README with usage examples and features. --- .../azure-container-apps-dab-starter.ps1 | 134 +++++++--- samples/azure/readme.md | 234 +++++++++++++----- 2 files changed, 272 insertions(+), 96 deletions(-) diff --git a/samples/azure/azure-container-apps-dab-starter.ps1 b/samples/azure/azure-container-apps-dab-starter.ps1 index 2e8843e5d4..27114d6add 100644 --- a/samples/azure/azure-container-apps-dab-starter.ps1 +++ b/samples/azure/azure-container-apps-dab-starter.ps1 @@ -42,7 +42,16 @@ Minimum number of container replicas. Default: 1 .PARAMETER MaxReplicas - Maximum number of container replicas. Default: 3`n`n.PARAMETER DabConfigFile`n Path to DAB configuration file. Default: src/Service.Tests/dab-config.MsSql.json + Maximum number of container replicas. Default: 3 + +.PARAMETER ContainerCpu + Number of CPU cores for each container replica. Default: 0.5 + +.PARAMETER ContainerMemory + Memory in GB for each container replica. Default: 1.0 + +.PARAMETER DabConfigFile + Path to DAB configuration file. Default: src/Service.Tests/dab-config.MsSql.json .EXAMPLE .\azure-container-apps-dab-starter.ps1 @@ -52,6 +61,10 @@ .\azure-container-apps-dab-starter.ps1 -ResourcePrefix "mydab" -Location "westus2" Deploys to West US 2 with custom prefix +.EXAMPLE + .\azure-container-apps-dab-starter.ps1 -ContainerCpu 1 -ContainerMemory 2 + Deploys with custom CPU and memory settings + .EXAMPLE .\azure-container-apps-dab-starter.ps1 -SkipCleanup Deploys and keeps resources even if errors occur @@ -101,6 +114,14 @@ param( [ValidateRange(1, 30)] [int]$MaxReplicas = 3, + [Parameter(Mandatory=$false)] + [ValidateRange(0.25, 4)] + [double]$ContainerCpu = 0.5, + + [Parameter(Mandatory=$false)] + [ValidateRange(0.5, 8)] + [double]$ContainerMemory = 1.0, + [Parameter(Mandatory=$false)] [string]$DabConfigFile ) @@ -149,15 +170,9 @@ function Test-Prerequisites { exit 1 } - # Check Azure CLI containerapp extension (ignore metadata permission errors) - $extensionCheck = az extension list --query "[?name=='containerapp'].name" -o tsv 2>$null - if ([string]::IsNullOrEmpty($extensionCheck)) { - Write-ErrorMessage "Azure CLI containerapp extension is not installed." - Write-Host "Please run: az extension add --name containerapp --upgrade --yes" -ForegroundColor Yellow - Write-Host "If permission errors occur, run PowerShell as Administrator." -ForegroundColor Yellow - exit 1 - } - Write-Success "Azure CLI containerapp extension installed" + # Skip containerapp extension check due to known Azure CLI metadata bug + # The commands work despite the PermissionError on dist-info files + Write-Host "[INFO] Skipping containerapp extension check (Azure CLI metadata issue)" -ForegroundColor Yellow # Check if Docker is running try { @@ -184,13 +199,23 @@ function Test-Prerequisites { } Write-Success "sqlcmd installed" - # Check if logged into Azure - $account = az account show 2>$null | ConvertFrom-Json - if (-not $account) { + # Check if logged into Azure (workaround for containerapp extension errors) + $oldErrorPref = $ErrorActionPreference + $ErrorActionPreference = "SilentlyContinue" + $accountJson = az account show --only-show-errors 2>$null | Out-String + $ErrorActionPreference = $oldErrorPref + + if ($accountJson -and $accountJson -match '\{') { + try { + $account = $accountJson | ConvertFrom-Json + Write-Success "Logged into Azure as $($account.user.name)" + } catch { + Write-Warning "Azure CLI working but cannot parse account info (continuing anyway)" + } + } else { Write-ErrorMessage "Not logged into Azure. Please run: az login" exit 1 } - Write-Success "Logged into Azure as $($account.user.name)" } function Get-UniqueResourceName { @@ -362,6 +387,8 @@ Write-Host " SQL Admin User: $sqlAdminUser" Write-Host " Container Port: $ContainerPort" Write-Host " Min Replicas: $MinReplicas" Write-Host " Max Replicas: $MaxReplicas" +Write-Host " Container CPU: $ContainerCpu cores" +Write-Host " Container Memory: $ContainerMemory GB" $confirmation = Read-Host "`nProceed with deployment? (y/N)" if ($confirmation -ne 'y') { @@ -513,14 +540,14 @@ $output = sqlcmd -S $sqlServer -U $sqlAdminUser -P $sqlAdminPasswordPlain -d $sq Write-Host "Command: sqlcmd -S $sqlServer -U $sqlAdminUser -P *** -d $sqlDbName -i $sqlScriptPath -I" -ForegroundColor Yellow } - # Prepare DAB config with actual connection string (same as ACI) + # Prepare DAB config with actual connection string Write-Step "Preparing DAB configuration..." $dabConfigPath = Join-Path $env:TEMP "dab-config.json" $connectionString = "Server=$sqlServer,1433;Persist Security Info=False;User ID=$sqlAdminUser;Password=$sqlAdminPasswordPlain;Initial Catalog=$sqlDbName;MultipleActiveResultSets=False;Connection Timeout=30;TrustServerCertificate=True;" Update-DabConfigFile -SourceConfigPath $DabConfigFile -ConnectionString $connectionString -OutputPath $dabConfigPath - # Upload config to Azure Storage Account for container to download (same as ACI) + # Upload config to Azure Storage Account for container to download Write-Step "Creating storage account for config file..." $storageAccountName = "dabstorage$(Get-Random -Minimum 10000 -Maximum 99999)" @@ -539,7 +566,7 @@ $output = sqlcmd -S $sqlServer -U $sqlAdminUser -P $sqlAdminPasswordPlain -d $sq --account-name $storageAccountName ` --query "[0].value" -o tsv - # Create container (blob container, not ACA) + # Create blob container az storage container create ` --name "config" ` --account-name $storageAccountName ` @@ -558,14 +585,14 @@ $output = sqlcmd -S $sqlServer -U $sqlAdminUser -P $sqlAdminPasswordPlain -d $sq # Generate SAS URL for the blob (valid for 1 year) $expiryDate = (Get-Date).AddYears(1).ToString("yyyy-MM-ddTHH:mm:ssZ") - $configUrl = az storage blob generate-sas ` + $configUrl = (az storage blob generate-sas ` --account-name $storageAccountName ` --account-key $storageKey ` --container-name "config" ` --name "dab-config.json" ` --permissions r ` --expiry $expiryDate ` - --full-uri -o tsv + --full-uri -o tsv 2>&1 | Where-Object { $_ -notmatch 'UserWarning' -and $_ -notmatch 'pkg_resources' }) -join '' Write-Success "Generated SAS URL for config download" @@ -577,11 +604,39 @@ $output = sqlcmd -S $sqlServer -U $sqlAdminUser -P $sqlAdminPasswordPlain -d $sq --location $Location | Out-Null Write-Success "Container Apps Environment created" - # Deploy Container App (same blob storage + curl approach as ACI) + # Deploy Container App with startup command to download config Write-Step "Deploying Container App..." - Write-Host "Note: Container will download config from blob storage on startup (same as ACI)" -ForegroundColor Yellow - - # Use secrets for CONFIG_URL to avoid command-line shell parsing of SAS URL special characters + Write-Host "Note: Container will download config from blob storage on startup" -ForegroundColor Yellow + + # Create a YAML configuration file for proper command/args setup + # Using YAML ensures command and args are properly formatted as arrays + $containerYamlPath = Join-Path $env:TEMP "container-app.yaml" + $containerYaml = @" +properties: + template: + containers: + - name: $acaName + image: ${acrLoginServer}/${acrImageName}:${acrImageTag} + command: + - /bin/sh + args: + - -c + - curl -o /App/dab-config.json "`$CONFIG_URL" && dotnet Azure.DataApiBuilder.Service.dll --ConfigFileName /App/dab-config.json + env: + - name: ASPNETCORE_URLS + value: http://+:$ContainerPort + - name: CONFIG_URL + value: $configUrl + resources: + cpu: $ContainerCpu + memory: ${ContainerMemory}Gi + scale: + minReplicas: $MinReplicas + maxReplicas: $MaxReplicas +"@ + $containerYaml | Out-File -FilePath $containerYamlPath -Encoding UTF8 + + # Create the container app with basic settings first az containerapp create ` --name $acaName ` --resource-group $ResourceGroup ` @@ -590,16 +645,16 @@ $output = sqlcmd -S $sqlServer -U $sqlAdminUser -P $sqlAdminPasswordPlain -d $sq --target-port $ContainerPort ` --ingress external ` --transport auto ` - --min-replicas $MinReplicas ` - --max-replicas $MaxReplicas ` - --cpu 0.5 ` - --memory 1.0Gi ` --registry-server $acrLoginServer ` --registry-username $acrName ` - --registry-password $acrPassword ` - --secrets "config-url=$configUrl" ` - --env-vars "ASPNETCORE_URLS=http://+:$ContainerPort" "CONFIG_URL=secretref:config-url" ` - --command "/bin/sh" "-c" "curl -o /App/dab-config.json \"\$CONFIG_URL\" && dotnet Azure.DataApiBuilder.Service.dll --ConfigFileName /App/dab-config.json" | Out-Null + --registry-password $acrPassword | Out-Null + + # Update the container app with the YAML configuration for command/args/env + Write-Host "Configuring container startup command..." -ForegroundColor Yellow + az containerapp update ` + --name $acaName ` + --resource-group $ResourceGroup ` + --yaml $containerYamlPath | Out-Null Write-Success "Container App deployed" @@ -632,13 +687,26 @@ $output = sqlcmd -S $sqlServer -U $sqlAdminUser -P $sqlAdminPasswordPlain -d $sq Write-Host " Admin User: $sqlAdminUser" Write-Host " Admin Password: ********** (saved to file earlier)" Write-Host "" + Write-Host "Container Resources:" -ForegroundColor Cyan + Write-Host " Min Replicas: $MinReplicas" + Write-Host " Max Replicas: $MaxReplicas" + Write-Host " CPU: $ContainerCpu cores" + Write-Host " Memory: $ContainerMemory GB" + Write-Host "" Write-Host "Try these commands:" -ForegroundColor Cyan Write-Host " # List all publishers" Write-Host " curl https://$appUrl/api/Publisher" Write-Host "" Write-Host " # GraphQL query" - $curlCmd = ' curl https://{0}/graphql -H "Content-Type: application/json" -d "{\"query\":\"{{publishers{{items{{id name}}}}}}\""' -f $appUrl - Write-Host $curlCmd + Write-Host ' curl https://'$appUrl'/graphql -H "Content-Type: application/json" -d "{\"query\":\"{publishers{items{id name}}}\"}""' + Write-Host "" + Write-Host " # View container logs" + Write-Host " az containerapp logs show --name $acaName --resource-group $ResourceGroup --follow" + Write-Host "" + Write-Host "Storage Details:" -ForegroundColor Cyan + Write-Host " Storage Account: $storageAccountName" + Write-Host " Blob Container: config" + Write-Host " Config File: dab-config.json" Write-Host "" Write-Host "Configuration file generated at: $dabConfigPath" -ForegroundColor Yellow Write-Host "" diff --git a/samples/azure/readme.md b/samples/azure/readme.md index 915f06bb6e..86f627ec90 100644 --- a/samples/azure/readme.md +++ b/samples/azure/readme.md @@ -12,52 +12,82 @@ We provide two comprehensive PowerShell starter scripts that create a complete D Deploys DAB to **Azure Container Apps** with auto-scaling, HTTPS, and health monitoring. ```powershell -# Quick start with auto-generated names -.\azure-container-apps-dab-starter.ps1 - -# Custom deployment -.\azure-container-apps-dab-starter.ps1 -ResourcePrefix "mydab" -Location "westus2" +# Basic deployment (required parameters) +$password = ConvertTo-SecureString "" -AsPlainText -Force +.\azure-container-apps-dab-starter.ps1 -SubscriptionId "" ` + -ResourceGroup "rg-dab-aca" ` + -ResourcePrefix "mydab" ` + -SqlAdminPassword $password + +# Custom location and resources +$password = ConvertTo-SecureString "" -AsPlainText -Force +.\azure-container-apps-dab-starter.ps1 -SubscriptionId "" ` + -ResourceGroup "rg-dab-aca" ` + -ResourcePrefix "mydab" ` + -SqlAdminPassword $password ` + -Location "westus2" ` + -ContainerCpu 1 ` + -ContainerMemory 2 ``` **Features:** -- ✅ Auto-scaling (configurable min/max replicas) -- ✅ HTTPS enabled by default -- ✅ Built-in health probes -- ✅ Managed environment +- ✅ Auto-scaling (configurable 0-30 replicas) +- ✅ HTTPS enabled by default with automatic certificate management +- ✅ Built-in health probes and monitoring +- ✅ Managed Container Apps Environment with Log Analytics +- ✅ Zero-downtime deployments with revision management +- ✅ Configurable CPU (0.25-4 cores) and memory (0.5-8 GB) +- ✅ Container startup command downloads config from Azure Blob Storage (SAS URL) - ✅ Better for production workloads #### **[azure-container-instances-dab-starter.ps1](./azure-container-instances-dab-starter.ps1)** Deploys DAB to **Azure Container Instances** for simpler, single-instance deployments. ```powershell -# Quick start -.\azure-container-instances-dab-starter.ps1 - -# With custom resources -.\azure-container-instances-dab-starter.ps1 -ContainerCpu 2 -ContainerMemory 3 +# Basic deployment (required parameters) +$password = ConvertTo-SecureString "" -AsPlainText -Force +.\azure-container-instances-dab-starter.ps1 -SubscriptionId "" ` + -ResourceGroup "rg-dab-aci" ` + -ResourcePrefix "mydab" ` + -SqlAdminPassword $password + +# With custom CPU and memory +$password = ConvertTo-SecureString "" -AsPlainText -Force +.\azure-container-instances-dab-starter.ps1 -SubscriptionId "" ` + -ResourceGroup "rg-dab-aci" ` + -ResourcePrefix "mydab" ` + -SqlAdminPassword $password ` + -ContainerCpu 2 ` + -ContainerMemory 3 ``` **Features:** -- ✅ Simpler architecture -- ✅ Faster startup -- ✅ Lower cost for testing -- ✅ Good for development/testing +- ✅ Simpler architecture (single container) +- ✅ Faster startup (~30 seconds) +- ✅ Lower cost for testing and development +- ✅ Configurable CPU (1-4 cores) and memory (0.5-16 GB) +- ✅ ARM template-based deployment +- ✅ Good for development, testing, and simple workloads ### What These Scripts Do Both starter scripts automatically: -1. **Validate Prerequisites** - Check for Azure CLI, Docker, sqlcmd -2. **Create Azure Resources**: +1. **Validate Prerequisites** - Check for Azure CLI, Docker, sqlcmd, and verify Docker is running +2. **Verify Subscription** - Set or confirm the Azure subscription to use +3. **Create Azure Resources**: - Resource Group - - Azure Container Registry (ACR) - - MS SQL Server & Database - - Container deployment (Apps or Instances) -3. **Build & Deploy** - Build DAB Docker image and push to ACR -4. **Configure Security** - Set up SQL firewall rules (Azure services + your IP) -5. **Load Test Data** - Import sample database schema and data from `src/Service.Tests/DatabaseSchema-MsSql.sql` -6. **Generate Config** - Create a working DAB configuration file -7. **Provide Endpoints** - Display all connection details and example commands + - Azure Container Registry (ACR) with admin user enabled + - MS SQL Server & Database (with configurable service tier) + - **Container Apps**: Storage Account (for config file), Container Apps Environment with Log Analytics, Container App + - **Container Instances**: Container Instance deployed via ARM template +4. **Build & Deploy** - Build DAB Docker image from repository root and push to ACR +5. **Configure Security** - Set up SQL firewall rules (Azure services + your public IP) +6. **Load Test Data** - Import sample database schema and data from `src/Service.Tests/DatabaseSchema-MsSql.sql` using sqlcmd +7. **Generate Config** - Create a working DAB configuration file with connection string and upload to blob storage (Container Apps) or embed in deployment (Container Instances) +8. **Deploy Container** - Start container with proper startup command and environment variables +9. **Verify Deployment** - Wait for resources to be ready and display status +10. **Provide Endpoints** - Display all connection details, URLs, and example curl commands ### Prerequisites @@ -75,40 +105,64 @@ Before running these scripts, ensure you have: # 1. Login to Azure az login -# 2. Clone the repository (if not already done) +# 2. Get your subscription ID (optional - script can use current subscription) +az account show --query id -o tsv + +# 3. Clone the repository (if not already done) git clone https://github.com/Azure/data-api-builder.git cd data-api-builder/samples/azure -# 3. Run the script -.\azure-container-apps-dab-starter.ps1 +# 4. Prepare SQL password +$password = ConvertTo-SecureString "" -AsPlainText -Force + +# 5. Run the script with required parameters +.\azure-container-apps-dab-starter.ps1 ` + -SubscriptionId "" ` + -ResourceGroup "rg-dab-demo" ` + -ResourcePrefix "dab" ` + -SqlAdminPassword $password # The script will: -# - Auto-generate resource names +# - Validate prerequisites (Azure CLI, Docker, sqlcmd) # - Show a deployment summary # - Ask for confirmation before proceeding -# - Display connection details after deployment +# - Create all Azure resources +# - Build and deploy DAB container +# - Load test data from DatabaseSchema-MsSql.sql +# - Display connection details and example commands ``` ### Script Parameters -Both scripts support extensive customization: +#### Required Parameters (Both Scripts) +```powershell +-SubscriptionId # Azure subscription ID +-ResourceGroup # Resource group name +-ResourcePrefix # Prefix for all resource names (generates unique names) +-SqlAdminPassword # SQL Server admin password (SecureString) +``` +#### Optional Parameters (Both Scripts) ```powershell -# Common Parameters --SubscriptionId # Azure subscription (uses current if not specified) --ResourceGroup # Resource group name (auto-generated if not provided) -Location # Azure region (default: eastus) --ResourcePrefix # Prefix for all resource names (auto-generated if not provided) --SqlAdminPassword # SQL admin password (auto-generated if not provided) + # Options: eastus, eastus2, westus, westus2, westus3, + # centralus, northeurope, westeurope, uksouth, southeastasia -ContainerPort # DAB container port (default: 5000) -SqlServiceTier # SQL DB tier: Basic, S0, S1, S2, P1, P2 (default: S0) +-DabConfigFile # Path to DAB config file (default: src/Service.Tests/dab-config.MsSql.json) -SkipCleanup # Keep resources even if deployment fails +``` -# Container Apps Specific --MinReplicas # Minimum replicas (default: 1) --MaxReplicas # Maximum replicas (default: 3) +#### Container Apps Specific Parameters +```powershell +-MinReplicas # Minimum replicas: 0-30 (default: 1) +-MaxReplicas # Maximum replicas: 1-30 (default: 3) +-ContainerCpu # CPU cores: 0.25-4 (default: 0.5) +-ContainerMemory # Memory in GB: 0.5-8 (default: 1.0) +``` -# Container Instances Specific +#### Container Instances Specific Parameters +```powershell -ContainerCpu # CPU cores: 1-4 (default: 1) -ContainerMemory # Memory in GB: 0.5-16 (default: 1.5) ``` @@ -116,23 +170,52 @@ Both scripts support extensive customization: ### Examples ```powershell -# Minimal - uses all defaults with auto-generation -.\azure-container-apps-dab-starter.ps1 - -# Custom prefix and location -.\azure-container-apps-dab-starter.ps1 -ResourcePrefix "mydab" -Location "westus2" - -# Specific subscription and resource group -.\azure-container-apps-dab-starter.ps1 -SubscriptionId "xxx-xxx" -ResourceGroup "my-rg" - -# Production configuration with scaling -.\azure-container-apps-dab-starter.ps1 -SqlServiceTier "S2" -MinReplicas 2 -MaxReplicas 10 +# Prepare password once +$password = ConvertTo-SecureString "" -AsPlainText -Force + +# Basic Container Apps deployment +.\azure-container-apps-dab-starter.ps1 ` + -SubscriptionId "" ` + -ResourceGroup "rg-dab-aca" ` + -ResourcePrefix "mydab" ` + -SqlAdminPassword $password + +# Custom location and region +.\azure-container-apps-dab-starter.ps1 ` + -SubscriptionId "" ` + -ResourceGroup "rg-dab-westus" ` + -ResourcePrefix "mydab" ` + -SqlAdminPassword $password ` + -Location "westus2" + +# Production configuration with scaling and higher tier SQL +.\azure-container-apps-dab-starter.ps1 ` + -SubscriptionId "" ` + -ResourceGroup "rg-dab-prod" ` + -ResourcePrefix "proddb" ` + -SqlAdminPassword $password ` + -SqlServiceTier "S2" ` + -MinReplicas 2 ` + -MaxReplicas 10 ` + -ContainerCpu 1 ` + -ContainerMemory 2 # High-performance Container Instance -.\azure-container-instances-dab-starter.ps1 -ContainerCpu 4 -ContainerMemory 8 +.\azure-container-instances-dab-starter.ps1 ` + -SubscriptionId "" ` + -ResourceGroup "rg-dab-aci" ` + -ResourcePrefix "mydab" ` + -SqlAdminPassword $password ` + -ContainerCpu 4 ` + -ContainerMemory 8 # Keep resources on failure for debugging -.\azure-container-apps-dab-starter.ps1 -SkipCleanup +.\azure-container-apps-dab-starter.ps1 ` + -SubscriptionId "" ` + -ResourceGroup "rg-dab-debug" ` + -ResourcePrefix "debug" ` + -SqlAdminPassword $password ` + -SkipCleanup ``` ### After Deployment @@ -146,20 +229,45 @@ Once deployed, you'll receive a summary with: Example output: ``` +========================================================================== + Deployment Summary +========================================================================== + DAB Endpoints: - App URL: https://dab-aca-abc123.eastus.azurecontainerapps.io - REST API: https://dab-aca-abc123.eastus.azurecontainerapps.io/api - GraphQL: https://dab-aca-abc123.eastus.azurecontainerapps.io/graphql - Health Check: https://dab-aca-abc123.eastus.azurecontainerapps.io/health + App URL: https://mydab-aca-abc123.westus2.azurecontainerapps.io + Health Check: https://mydab-aca-abc123.westus2.azurecontainerapps.io/ + REST API: https://mydab-aca-abc123.westus2.azurecontainerapps.io/api + GraphQL: https://mydab-aca-abc123.westus2.azurecontainerapps.io/graphql + +Database Connection: + Server: mydab-sql-xyz789.database.windows.net + Database: dabdb + Username: sqladmin + Password: + +Container Configuration: + Image: mydabacr123.azurecr.io/dab:latest + CPU: 1 cores + Memory: 2 GB + Replicas: 1-3 (min-max) Try these commands: + # Test health endpoint + curl https://mydab-aca-abc123.westus2.azurecontainerapps.io/ + # List all publishers - curl https://dab-aca-abc123.eastus.azurecontainerapps.io/api/Publisher + curl https://mydab-aca-abc123.westus2.azurecontainerapps.io/api/Publisher + + # Get a specific book + curl https://mydab-aca-abc123.westus2.azurecontainerapps.io/api/Book/id/1 # GraphQL query - curl https://dab-aca-abc123.eastus.azurecontainerapps.io/graphql \ + curl https://mydab-aca-abc123.westus2.azurecontainerapps.io/graphql \ -H 'Content-Type: application/json' \ - -d '{"query":"{publishers{items{id name}}}"}' + -d '{"query":"{books{items{id title year}}}"}' + +Cleanup: + az group delete --name rg-dab-aca --yes --no-wait ``` ### Cleanup From 4ad014a873eb5701737d9e5246d7a641f8806276 Mon Sep 17 00:00:00 2001 From: souvikghosh04 Date: Fri, 19 Dec 2025 16:24:25 +0530 Subject: [PATCH 4/7] Enhance DAB starter scripts with required parameters, improved descriptions, and updated README with usage examples for better clarity and usability. --- .../azure-container-apps-dab-starter.ps1 | 127 ++++++++++++------ .../azure-container-instances-dab-starter.ps1 | 67 ++++++--- samples/azure/readme.md | 34 +++-- 3 files changed, 162 insertions(+), 66 deletions(-) diff --git a/samples/azure/azure-container-apps-dab-starter.ps1 b/samples/azure/azure-container-apps-dab-starter.ps1 index 27114d6add..e386d1a948 100644 --- a/samples/azure/azure-container-apps-dab-starter.ps1 +++ b/samples/azure/azure-container-apps-dab-starter.ps1 @@ -15,58 +15,88 @@ - Generates DAB configuration file .PARAMETER SubscriptionId - Azure subscription ID. If not provided, uses the current active subscription. + [Required] Azure subscription ID where all resources will be deployed. + You can find this with: az account show --query id -o tsv .PARAMETER ResourceGroup - Name of the resource group. Auto-generates if not provided. + [Required] Name of the resource group to create or use for all deployed resources. + If the resource group exists, resources will be added to it. If not, it will be created. .PARAMETER Location - Azure region (e.g., eastus, westus2). Default: eastus + [Optional] Azure region where resources will be deployed. Default: eastus + Supported regions: eastus, eastus2, westus, westus2, westus3, centralus, + northeurope, westeurope, uksouth, southeastasia .PARAMETER ResourcePrefix - Prefix for resource names. Auto-generates if not provided. + [Optional] Prefix used to generate unique names for all Azure resources. Default: ACA + Example: 'mydab' creates resources like mydab-acr-a1b2c3, mydab-sql-a1b2c3, mydab-aca-a1b2c3 + If not provided, uses 'ACA' as the prefix for Azure Container Apps deployment. .PARAMETER SqlAdminPassword - SQL Server admin password. Auto-generates a secure password if not provided. + [Required] SQL Server administrator password as a SecureString. + Must meet Azure SQL Server password requirements: 8-128 characters, mix of uppercase, + lowercase, numbers, and special characters. + Create with: $password = ConvertTo-SecureString "YourPassword" -AsPlainText -Force .PARAMETER SkipCleanup - If set, resources won't be deleted on errors. + [Optional] Switch to keep resources even if deployment fails. + By default, the script cleans up resources on errors. Use this flag to preserve + resources for debugging. .PARAMETER ContainerPort - Port for the DAB container. Default: 5000 + [Optional] Port number where DAB will listen for HTTP requests. Default: 5000 + This is the internal container port. External access is via HTTPS on port 443. .PARAMETER SqlServiceTier - SQL Database service tier. Default: S0 + [Optional] Azure SQL Database service tier/SKU. Default: S0 + Options: Basic, S0, S1, S2, P1, P2 + - Basic: 5 DTUs, up to 2GB storage + - S0: 10 DTUs, up to 250GB storage + - S1: 20 DTUs, up to 250GB storage + - S2: 50 DTUs, up to 250GB storage + - P1: 125 DTUs, up to 500GB storage + - P2: 250 DTUs, up to 500GB storage .PARAMETER MinReplicas - Minimum number of container replicas. Default: 1 + [Optional] Minimum number of container replicas to maintain. Default: 1, Range: 0-30 + Set to 0 to scale to zero when idle (saves costs but adds cold start latency). + Container Apps will automatically scale between MinReplicas and MaxReplicas based on load. .PARAMETER MaxReplicas - Maximum number of container replicas. Default: 3 + [Optional] Maximum number of container replicas for auto-scaling. Default: 3, Range: 1-30 + Container Apps will scale up to this number based on CPU, memory, or HTTP request load. + Must be greater than or equal to MinReplicas. .PARAMETER ContainerCpu - Number of CPU cores for each container replica. Default: 0.5 + [Optional] Number of CPU cores allocated to each container replica. Default: 0.5, Range: 0.25-4 + Options: 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0, 3.5, 4.0 + Higher values provide better performance but increase costs. .PARAMETER ContainerMemory - Memory in GB for each container replica. Default: 1.0 + [Optional] Memory in GB allocated to each container replica. Default: 1.0, Range: 0.5-8 + Must be paired appropriately with CPU (e.g., 0.5 CPU can use 0.5-4 GB memory). + Minimum ratio: 0.5 GB per 0.25 CPU cores. .PARAMETER DabConfigFile - Path to DAB configuration file. Default: src/Service.Tests/dab-config.MsSql.json + [Optional] Path to the DAB configuration JSON file. Default: src/Service.Tests/dab-config.MsSql.json + The script will replace the connection string placeholder with actual SQL Server credentials. + Use a custom config file if you need different entity mappings or security settings. .EXAMPLE - .\azure-container-apps-dab-starter.ps1 - Runs with auto-generated values and prompts for confirmation + $password = ConvertTo-SecureString "YourPassword123!" -AsPlainText -Force + .\azure-container-apps-dab-starter.ps1 -SubscriptionId "abc123" -ResourceGroup "rg-dab" -SqlAdminPassword $password + Basic deployment using default 'ACA' prefix .EXAMPLE - .\azure-container-apps-dab-starter.ps1 -ResourcePrefix "mydab" -Location "westus2" + .\azure-container-apps-dab-starter.ps1 -SubscriptionId "abc123" -ResourceGroup "rg-dab" -ResourcePrefix "mydab" -SqlAdminPassword $password -Location "westus2" Deploys to West US 2 with custom prefix .EXAMPLE - .\azure-container-apps-dab-starter.ps1 -ContainerCpu 1 -ContainerMemory 2 - Deploys with custom CPU and memory settings + .\azure-container-apps-dab-starter.ps1 -SubscriptionId "abc123" -ResourceGroup "rg-dab" -SqlAdminPassword $password -ContainerCpu 1 -ContainerMemory 2 -MinReplicas 2 -MaxReplicas 10 + Deploys with custom CPU, memory, and scaling configuration .EXAMPLE - .\azure-container-apps-dab-starter.ps1 -SkipCleanup + .\azure-container-apps-dab-starter.ps1 -SubscriptionId "abc123" -ResourceGroup "rg-dab" -SqlAdminPassword $password -SkipCleanup Deploys and keeps resources even if errors occur .NOTES @@ -90,7 +120,7 @@ param( [ValidateSet('eastus', 'eastus2', 'westus', 'westus2', 'westus3', 'centralus', 'northeurope', 'westeurope', 'uksouth', 'southeastasia')] [string]$Location = "eastus", - [Parameter(Mandatory=$true)] + [Parameter(Mandatory=$false)] [string]$ResourcePrefix, [Parameter(Mandatory=$true)] @@ -129,6 +159,12 @@ param( $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" +# Auto-generate ResourcePrefix if not provided +if ([string]::IsNullOrEmpty($ResourcePrefix)) { + $ResourcePrefix = "ACA" + Write-Host "[INFO] Using default ResourcePrefix: $ResourcePrefix" -ForegroundColor Yellow +} + # Validate replica configuration if ($MaxReplicas -lt $MinReplicas) { Write-Host "[ERROR] MaxReplicas ($MaxReplicas) must be greater than or equal to MinReplicas ($MinReplicas)" -ForegroundColor Red @@ -585,14 +621,24 @@ $output = sqlcmd -S $sqlServer -U $sqlAdminUser -P $sqlAdminPasswordPlain -d $sq # Generate SAS URL for the blob (valid for 1 year) $expiryDate = (Get-Date).AddYears(1).ToString("yyyy-MM-ddTHH:mm:ssZ") - $configUrl = (az storage blob generate-sas ` + $ErrorActionPreference = "Continue" + $sasOutput = az storage blob generate-sas ` --account-name $storageAccountName ` --account-key $storageKey ` --container-name "config" ` --name "dab-config.json" ` --permissions r ` --expiry $expiryDate ` - --full-uri -o tsv 2>&1 | Where-Object { $_ -notmatch 'UserWarning' -and $_ -notmatch 'pkg_resources' }) -join '' + --full-uri -o tsv 2>&1 + $ErrorActionPreference = "Stop" + + # Filter out Python warnings and get only the URL + $configUrl = ($sasOutput | Where-Object { $_ -notmatch 'UserWarning' -and $_ -notmatch 'pkg_resources' -and $_ -notmatch 'Lib\\site-packages' -and $_.Length -gt 0 }) -join '' + + if (-not $configUrl -or $configUrl -notmatch '^https://') { + Write-ErrorMessage "Failed to generate valid SAS URL" + throw "SAS URL generation failed" + } Write-Success "Generated SAS URL for config download" @@ -608,11 +654,25 @@ $output = sqlcmd -S $sqlServer -U $sqlAdminUser -P $sqlAdminPasswordPlain -d $sq Write-Step "Deploying Container App..." Write-Host "Note: Container will download config from blob storage on startup" -ForegroundColor Yellow - # Create a YAML configuration file for proper command/args setup - # Using YAML ensures command and args are properly formatted as arrays + # Create complete YAML configuration file for container app + # Using YAML ensures command, args, and env vars are properly formatted $containerYamlPath = Join-Path $env:TEMP "container-app.yaml" $containerYaml = @" +location: $Location properties: + managedEnvironmentId: /subscriptions/$SubscriptionId/resourceGroups/$ResourceGroup/providers/Microsoft.App/managedEnvironments/$envName + configuration: + ingress: + external: true + targetPort: $ContainerPort + transport: auto + registries: + - server: $acrLoginServer + username: $acrName + passwordSecretRef: acr-password + secrets: + - name: acr-password + value: $acrPassword template: containers: - name: $acaName @@ -636,22 +696,9 @@ properties: "@ $containerYaml | Out-File -FilePath $containerYamlPath -Encoding UTF8 - # Create the container app with basic settings first + # Create the container app with complete YAML configuration + Write-Host "Creating container app with startup command..." -ForegroundColor Yellow az containerapp create ` - --name $acaName ` - --resource-group $ResourceGroup ` - --environment $envName ` - --image "${acrLoginServer}/${acrImageName}:${acrImageTag}" ` - --target-port $ContainerPort ` - --ingress external ` - --transport auto ` - --registry-server $acrLoginServer ` - --registry-username $acrName ` - --registry-password $acrPassword | Out-Null - - # Update the container app with the YAML configuration for command/args/env - Write-Host "Configuring container startup command..." -ForegroundColor Yellow - az containerapp update ` --name $acaName ` --resource-group $ResourceGroup ` --yaml $containerYamlPath | Out-Null @@ -698,7 +745,7 @@ properties: Write-Host " curl https://$appUrl/api/Publisher" Write-Host "" Write-Host " # GraphQL query" - Write-Host ' curl https://'$appUrl'/graphql -H "Content-Type: application/json" -d "{\"query\":\"{publishers{items{id name}}}\"}""' + Write-Host " curl https://$appUrl/graphql -H `"Content-Type: application/json`" -d `"{\`"query\`":\`"{publishers{items{id name}}}`\"}" Write-Host "" Write-Host " # View container logs" Write-Host " az containerapp logs show --name $acaName --resource-group $ResourceGroup --follow" diff --git a/samples/azure/azure-container-instances-dab-starter.ps1 b/samples/azure/azure-container-instances-dab-starter.ps1 index cce591ac41..df1f2dc0a6 100644 --- a/samples/azure/azure-container-instances-dab-starter.ps1 +++ b/samples/azure/azure-container-instances-dab-starter.ps1 @@ -14,49 +14,76 @@ - Generates DAB configuration file .PARAMETER SubscriptionId - Azure subscription ID. If not provided, uses the current active subscription. + [Required] Azure subscription ID where all resources will be deployed. + You can find this with: az account show --query id -o tsv .PARAMETER ResourceGroup - Name of the resource group. Auto-generates if not provided. + [Required] Name of the resource group to create or use for all deployed resources. + If the resource group exists, resources will be added to it. If not, it will be created. .PARAMETER Location - Azure region (e.g., eastus, westus2). Default: eastus + [Optional] Azure region where resources will be deployed. Default: eastus + Supported regions: eastus, eastus2, westus, westus2, westus3, centralus, + northeurope, westeurope, uksouth, southeastasia .PARAMETER ResourcePrefix - Prefix for resource names. Auto-generates if not provided. + [Optional] Prefix used to generate unique names for all Azure resources. Default: ACI + Example: 'mydab' creates resources like mydab-acr-a1b2c3, mydab-sql-a1b2c3, mydab-aci-a1b2c3 + If not provided, uses 'ACI' as the prefix for Azure Container Instances deployment. .PARAMETER SqlAdminPassword - SQL Server admin password. Auto-generates a secure password if not provided. + [Required] SQL Server administrator password as a SecureString. + Must meet Azure SQL Server password requirements: 8-128 characters, mix of uppercase, + lowercase, numbers, and special characters. + Create with: $password = ConvertTo-SecureString "YourPassword" -AsPlainText -Force .PARAMETER SkipCleanup - If set, resources won't be deleted on errors. + [Optional] Switch to keep resources even if deployment fails. + By default, the script cleans up resources on errors. Use this flag to preserve + resources for debugging. .PARAMETER ContainerPort - Port for the DAB container. Default: 5000 + [Optional] Port number where DAB will listen for HTTP requests. Default: 5000 + This is exposed publicly via the container's FQDN on HTTP (no HTTPS support in ACI). .PARAMETER SqlServiceTier - SQL Database service tier. Default: S0 + [Optional] Azure SQL Database service tier/SKU. Default: S0 + Options: Basic, S0, S1, S2, P1, P2 + - Basic: 5 DTUs, up to 2GB storage + - S0: 10 DTUs, up to 250GB storage + - S1: 20 DTUs, up to 250GB storage + - S2: 50 DTUs, up to 250GB storage + - P1: 125 DTUs, up to 500GB storage + - P2: 250 DTUs, up to 500GB storage .PARAMETER ContainerCpu - Number of CPU cores for the container. Default: 1 + [Optional] Number of CPU cores allocated to the container. Default: 1, Range: 1-4 + Options: 1, 2, 3, 4 + Note: Container Instances only supports integer CPU values (no fractional cores). + Higher values provide better performance but increase costs. .PARAMETER ContainerMemory - Memory in GB for the container. Default: 1.5 + [Optional] Memory in GB allocated to the container. Default: 1.5, Range: 0.5-16 + Common values: 0.5, 1.0, 1.5, 2.0, 4.0, 8.0, 16.0 + Must be paired appropriately with CPU (minimum 0.5 GB per CPU core recommended). .PARAMETER DabConfigFile - Path to DAB configuration file. Default: src/Service.Tests/dab-config.MsSql.json + [Optional] Path to the DAB configuration JSON file. Default: src/Service.Tests/dab-config.MsSql.json + The script will replace the connection string placeholder with actual SQL Server credentials. + Use a custom config file if you need different entity mappings or security settings. .EXAMPLE - .\azure-container-instances-dab-starter.ps1 - Runs with auto-generated values and prompts for confirmation + $password = ConvertTo-SecureString "YourPassword123!" -AsPlainText -Force + .\azure-container-instances-dab-starter.ps1 -SubscriptionId "abc123" -ResourceGroup "rg-dab" -SqlAdminPassword $password + Basic deployment using default 'ACI' prefix .EXAMPLE - .\azure-container-instances-dab-starter.ps1 -ResourcePrefix "mydab" -Location "westus2" + .\azure-container-instances-dab-starter.ps1 -SubscriptionId "abc123" -ResourceGroup "rg-dab" -ResourcePrefix "mydab" -SqlAdminPassword $password -Location "westus2" Deploys to West US 2 with custom prefix .EXAMPLE - .\azure-container-instances-dab-starter.ps1 -ContainerCpu 2 -ContainerMemory 3 - Deploys with custom CPU and memory settings + .\azure-container-instances-dab-starter.ps1 -SubscriptionId "abc123" -ResourceGroup "rg-dab" -SqlAdminPassword $password -ContainerCpu 4 -ContainerMemory 8 + Deploys with high-performance CPU and memory configuration .NOTES Prerequisites: @@ -79,7 +106,7 @@ param( [ValidateSet('eastus', 'eastus2', 'westus', 'westus2', 'westus3', 'centralus', 'northeurope', 'westeurope', 'uksouth', 'southeastasia')] [string]$Location = "eastus", - [Parameter(Mandatory=$true)] + [Parameter(Mandatory=$false)] [string]$ResourcePrefix, [Parameter(Mandatory=$true)] @@ -110,6 +137,12 @@ param( $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" +# Auto-generate ResourcePrefix if not provided +if ([string]::IsNullOrEmpty($ResourcePrefix)) { + $ResourcePrefix = "ACI" + Write-Host "[INFO] Using default ResourcePrefix: $ResourcePrefix" -ForegroundColor Yellow +} + # ============================================================================ # HELPER FUNCTIONS # ============================================================================ diff --git a/samples/azure/readme.md b/samples/azure/readme.md index 86f627ec90..bb15207447 100644 --- a/samples/azure/readme.md +++ b/samples/azure/readme.md @@ -105,21 +105,25 @@ Before running these scripts, ensure you have: # 1. Login to Azure az login -# 2. Get your subscription ID (optional - script can use current subscription) +# 2. Get your subscription ID az account show --query id -o tsv -# 3. Clone the repository (if not already done) +# 3. Clone the repository git clone https://github.com/Azure/data-api-builder.git cd data-api-builder/samples/azure -# 4. Prepare SQL password +# 4. Run the script with required parameters $password = ConvertTo-SecureString "" -AsPlainText -Force +.\azure-container-apps-dab-starter.ps1 ` + -SubscriptionId "" ` + -ResourceGroup "rg-dab-demo" ` + -SqlAdminPassword $password -# 5. Run the script with required parameters +# Or with custom prefix: .\azure-container-apps-dab-starter.ps1 ` -SubscriptionId "" ` -ResourceGroup "rg-dab-demo" ` - -ResourcePrefix "dab" ` + -ResourcePrefix "mydab" ` -SqlAdminPassword $password # The script will: @@ -138,12 +142,12 @@ $password = ConvertTo-SecureString "" -AsPlainText -Force ```powershell -SubscriptionId # Azure subscription ID -ResourceGroup # Resource group name --ResourcePrefix # Prefix for all resource names (generates unique names) -SqlAdminPassword # SQL Server admin password (SecureString) ``` #### Optional Parameters (Both Scripts) ```powershell +-ResourcePrefix # Prefix for all resource names (default: 'ACA' for Container Apps, 'ACI' for Container Instances) -Location # Azure region (default: eastus) # Options: eastus, eastus2, westus, westus2, westus3, # centralus, northeurope, westeurope, uksouth, southeastasia @@ -170,10 +174,16 @@ $password = ConvertTo-SecureString "" -AsPlainText -Force ### Examples ```powershell -# Prepare password once +# Prepare password for all examples $password = ConvertTo-SecureString "" -AsPlainText -Force -# Basic Container Apps deployment +# Basic Container Apps deployment (uses default 'ACA' prefix) +.\azure-container-apps-dab-starter.ps1 ` + -SubscriptionId "" ` + -ResourceGroup "rg-dab-aca" ` + -SqlAdminPassword $password + +# Container Apps deployment with custom prefix .\azure-container-apps-dab-starter.ps1 ` -SubscriptionId "" ` -ResourceGroup "rg-dab-aca" ` @@ -200,7 +210,13 @@ $password = ConvertTo-SecureString "" -AsPlainText -Force -ContainerCpu 1 ` -ContainerMemory 2 -# High-performance Container Instance +# Basic Container Instance deployment (uses default 'ACI' prefix) +.\azure-container-instances-dab-starter.ps1 ` + -SubscriptionId "" ` + -ResourceGroup "rg-dab-aci" ` + -SqlAdminPassword $password + +# High-performance Container Instance with custom prefix .\azure-container-instances-dab-starter.ps1 ` -SubscriptionId "" ` -ResourceGroup "rg-dab-aci" ` From 6d5657794023388404643c6fe1e4c049e189dcc6 Mon Sep 17 00:00:00 2001 From: Souvik Ghosh Date: Fri, 19 Dec 2025 16:27:30 +0530 Subject: [PATCH 5/7] Fix capitalization in readme for consistency --- samples/azure/readme.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/samples/azure/readme.md b/samples/azure/readme.md index bb15207447..dc74656688 100644 --- a/samples/azure/readme.md +++ b/samples/azure/readme.md @@ -1,4 +1,4 @@ -# Azure Deployment Samples for Data API Builder +# Azure Deployment Samples for Data API builder This directory contains scripts and samples for deploying Data API builder (DAB) to various Azure services. @@ -326,10 +326,10 @@ sqlcmd -S .database.windows.net -U sqladmin -P -d dabdb -i sr For manual deployments with existing configurations: ### [azure-deploy.sh](./azure-deploy.sh) -Deploy Data API builder to Azure Container Instance as described in [Running Data API Builder in Azure](https://learn.microsoft.com/azure/data-api-builder/running-in-azure) +Deploy Data API builder to Azure Container Instance as described in [Running Data API builder in Azure](https://learn.microsoft.com/azure/data-api-builder/running-in-azure) ### [azure-container-apps-deploy.sh](./azure-container-apps-deploy.sh) -Deploy Data API builder to Azure Container Apps as described in [Running Data API Builder in Azure](https://learn.microsoft.com/azure/data-api-builder/running-in-azure) +Deploy Data API builder to Azure Container Apps as described in [Running Data API builder in Azure](https://learn.microsoft.com/azure/data-api-builder/running-in-azure) **Note:** These scripts require a valid `dab-config.json` file in the same directory. @@ -351,11 +351,11 @@ Deploy Data API builder to Azure Container Apps as described in [Running Data AP ## Additional Resources -- [Data API Builder Documentation](https://learn.microsoft.com/azure/data-api-builder/) +- [Data API builder Documentation](https://learn.microsoft.com/azure/data-api-builder/) - [Running DAB in Azure](https://learn.microsoft.com/azure/data-api-builder/running-in-azure) - [Azure Container Apps Documentation](https://learn.microsoft.com/azure/container-apps/) - [Azure Container Instances Documentation](https://learn.microsoft.com/azure/container-instances/) ## Contributing -If you encounter issues or have suggestions for improving these scripts, please [open an issue](https://github.com/Azure/data-api-builder/issues) or submit a pull request. \ No newline at end of file +If you encounter issues or have suggestions for improving these scripts, please [open an issue](https://github.com/Azure/data-api-builder/issues) or submit a pull request. From be351b6ac337afb01e4392eae53143827ca3a328 Mon Sep 17 00:00:00 2001 From: souvikghosh04 Date: Fri, 19 Dec 2025 16:32:10 +0530 Subject: [PATCH 6/7] Remove password generation logic from DAB starter scripts for SQL admin password --- .../azure-container-apps-dab-starter.ps1 | 36 ------------------- .../azure-container-instances-dab-starter.ps1 | 36 ------------------- 2 files changed, 72 deletions(-) diff --git a/samples/azure/azure-container-apps-dab-starter.ps1 b/samples/azure/azure-container-apps-dab-starter.ps1 index e386d1a948..107e1ee08f 100644 --- a/samples/azure/azure-container-apps-dab-starter.ps1 +++ b/samples/azure/azure-container-apps-dab-starter.ps1 @@ -260,17 +260,6 @@ function Get-UniqueResourceName { return "$Prefix-$Type-$random".ToLower() } -function New-RandomPassword { - param( - [int]$Length = 16 - ) - $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()' - $password = -join ((1..$Length) | ForEach-Object { $chars[(Get-Random -Maximum $chars.Length)] }) - # Ensure complexity requirements - $password = 'A1!' + $password - return $password -} - function Update-DabConfigFile { param( [string]$SourceConfigPath, @@ -376,31 +365,6 @@ $envName = "${acaName}-env" $acrImageName = "dab" $acrImageTag = "latest" -# Generate SQL password if not provided -if (-not $SqlAdminPassword) { - $generatedPassword = New-RandomPassword - $SqlAdminPassword = ConvertTo-SecureString -String $generatedPassword -AsPlainText -Force - - # Store password to file for secure retrieval - $passwordFile = Join-Path $env:TEMP "dab-sql-password-$(Get-Date -Format 'yyyyMMdd-HHmmss').txt" - $generatedPassword | Out-File -FilePath $passwordFile -NoNewline - - # Restrict file permissions to current user only (requires elevated privileges) - try { - $acl = Get-Acl $passwordFile - $acl.SetAccessRuleProtection($true, $false) - $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($env:USERNAME, "FullControl", "Allow") - $acl.SetAccessRule($accessRule) - Set-Acl $passwordFile $acl - } - catch { - Write-Warning "Could not restrict password file ACL permissions (requires admin rights)." - } - - Write-Warning "Auto-generated SQL password has been saved to: $passwordFile" - Write-Host "Please store this password securely and delete the file after saving it to your password manager." -ForegroundColor Yellow -} - # Convert SecureString to plain text with proper memory management $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SqlAdminPassword) try { diff --git a/samples/azure/azure-container-instances-dab-starter.ps1 b/samples/azure/azure-container-instances-dab-starter.ps1 index df1f2dc0a6..28fc2e11aa 100644 --- a/samples/azure/azure-container-instances-dab-starter.ps1 +++ b/samples/azure/azure-container-instances-dab-starter.ps1 @@ -218,17 +218,6 @@ function Get-UniqueResourceName { return "$Prefix-$Type-$random".ToLower() } -function New-RandomPassword { - param( - [int]$Length = 16 - ) - $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()' - $password = -join ((1..$Length) | ForEach-Object { $chars[(Get-Random -Maximum $chars.Length)] }) - # Ensure complexity requirements - $password = 'A1!' + $password - return $password -} - function Update-DabConfigFile { param( [string]$SourceConfigPath, @@ -335,31 +324,6 @@ $dnsLabel = $dnsLabel -replace "[^a-zA-Z0-9-]", "" # DNS labels must be alphanum $acrImageName = "dab" $acrImageTag = "latest" -# Generate SQL password if not provided -if (-not $SqlAdminPassword) { - $generatedPassword = New-RandomPassword - $SqlAdminPassword = ConvertTo-SecureString -String $generatedPassword -AsPlainText -Force - - # Store password to file for secure retrieval - $passwordFile = Join-Path $env:TEMP "dab-sql-password-$(Get-Date -Format 'yyyyMMdd-HHmmss').txt" - $generatedPassword | Out-File -FilePath $passwordFile -NoNewline - - # Restrict file permissions to current user only (requires elevated privileges) - try { - $acl = Get-Acl $passwordFile - $acl.SetAccessRuleProtection($true, $false) - $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($env:USERNAME, "FullControl", "Allow") - $acl.SetAccessRule($accessRule) - Set-Acl $passwordFile $acl - } - catch { - Write-Warning "Could not restrict password file ACL permissions (requires admin rights)." - } - - Write-Warning "Auto-generated SQL password has been saved to: $passwordFile" - Write-Host "Please store this password securely and delete the file after saving it to your password manager." -ForegroundColor Yellow -} - # Convert SecureString to plain text with proper memory management $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SqlAdminPassword) try { From cbdcda71ac22097d41f0d2cb8e0bfe19bf379dd4 Mon Sep 17 00:00:00 2001 From: souvikghosh04 Date: Mon, 22 Dec 2025 15:47:39 +0530 Subject: [PATCH 7/7] Update example password in DAB starter scripts and remove parameter generation logic for clarity --- .../azure/azure-container-apps-dab-starter.ps1 | 15 ++------------- .../azure-container-instances-dab-starter.ps1 | 13 +------------ 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/samples/azure/azure-container-apps-dab-starter.ps1 b/samples/azure/azure-container-apps-dab-starter.ps1 index 107e1ee08f..adb7ba6145 100644 --- a/samples/azure/azure-container-apps-dab-starter.ps1 +++ b/samples/azure/azure-container-apps-dab-starter.ps1 @@ -83,7 +83,7 @@ Use a custom config file if you need different entity mappings or security settings. .EXAMPLE - $password = ConvertTo-SecureString "YourPassword123!" -AsPlainText -Force + $password = ConvertTo-SecureString "XXXX" -AsPlainText -Force .\azure-container-apps-dab-starter.ps1 -SubscriptionId "abc123" -ResourceGroup "rg-dab" -SqlAdminPassword $password Basic deployment using default 'ACA' prefix @@ -331,17 +331,6 @@ if (-not (Test-Path $DabConfigFile)) { Write-Success "Repository root: $repoPath" Write-Success "Using DAB config: $DabConfigFile" -# Generate or validate parameters -if (-not $ResourcePrefix) { - $ResourcePrefix = "dab$(Get-Random -Maximum 9999)" - Write-Host "Generated resource prefix: $ResourcePrefix" -ForegroundColor Yellow -} - -if (-not $ResourceGroup) { - $ResourceGroup = "rg-$ResourcePrefix" - Write-Host "Generated resource group: $ResourceGroup" -ForegroundColor Yellow -} - # Set subscription if ($SubscriptionId) { Write-Step "Setting Azure subscription..." @@ -709,7 +698,7 @@ properties: Write-Host " curl https://$appUrl/api/Publisher" Write-Host "" Write-Host " # GraphQL query" - Write-Host " curl https://$appUrl/graphql -H `"Content-Type: application/json`" -d `"{\`"query\`":\`"{publishers{items{id name}}}`\"}" + Write-Host " curl https://$appUrl/graphql -H `"Content-Type: application/json`" -d `"{\`"query\`":\`"{publishers{items{id name}}}`\`"}`" Write-Host "" Write-Host " # View container logs" Write-Host " az containerapp logs show --name $acaName --resource-group $ResourceGroup --follow" diff --git a/samples/azure/azure-container-instances-dab-starter.ps1 b/samples/azure/azure-container-instances-dab-starter.ps1 index 28fc2e11aa..377f202314 100644 --- a/samples/azure/azure-container-instances-dab-starter.ps1 +++ b/samples/azure/azure-container-instances-dab-starter.ps1 @@ -73,7 +73,7 @@ Use a custom config file if you need different entity mappings or security settings. .EXAMPLE - $password = ConvertTo-SecureString "YourPassword123!" -AsPlainText -Force + $password = ConvertTo-SecureString "XXXX" -AsPlainText -Force .\azure-container-instances-dab-starter.ps1 -SubscriptionId "abc123" -ResourceGroup "rg-dab" -SqlAdminPassword $password Basic deployment using default 'ACI' prefix @@ -289,17 +289,6 @@ if (-not (Test-Path $DabConfigFile)) { Write-Success "Repository root: $repoPath" Write-Success "Using DAB config: $DabConfigFile" -# Generate or validate parameters -if (-not $ResourcePrefix) { - $ResourcePrefix = "dab$(Get-Random -Maximum 9999)" - Write-Host "Generated resource prefix: $ResourcePrefix" -ForegroundColor Yellow -} - -if (-not $ResourceGroup) { - $ResourceGroup = "rg-$ResourcePrefix" - Write-Host "Generated resource group: $ResourceGroup" -ForegroundColor Yellow -} - # Set subscription if ($SubscriptionId) { Write-Step "Setting Azure subscription..."