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..adb7ba6145 --- /dev/null +++ b/samples/azure/azure-container-apps-dab-starter.ps1 @@ -0,0 +1,733 @@ +<# +.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 + [Required] Azure subscription ID where all resources will be deployed. + You can find this with: az account show --query id -o tsv + +.PARAMETER ResourceGroup + [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 + [Optional] Azure region where resources will be deployed. Default: eastus + Supported regions: eastus, eastus2, westus, westus2, westus3, centralus, + northeurope, westeurope, uksouth, southeastasia + +.PARAMETER ResourcePrefix + [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 + [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 + [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 + [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 + [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 + [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 + [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 + [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 + [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 + [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 + $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 + +.EXAMPLE + .\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 -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 -SubscriptionId "abc123" -ResourceGroup "rg-dab" -SqlAdminPassword $password -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=$true)] + [string]$SubscriptionId, + + [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)] + [string]$ResourcePrefix, + + [Parameter(Mandatory=$true)] + [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, + + [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 +) + +$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 + exit 1 +} + +# ============================================================================ +# 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-ErrorMessage { + 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-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-ErrorMessage "Docker is not installed. Please install Docker Desktop from: https://www.docker.com/products/docker-desktop" + exit 1 + } + + # 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 { + $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-ErrorMessage "Docker is not running or not responding. Please start Docker Desktop." + Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red + 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 (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 + } +} + +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 Update-DabConfigFile { + param( + [string]$SourceConfigPath, + [string]$ConnectionString, + [string]$OutputPath + ) + + # 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" + } + + # 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" +} + +# ============================================================================ +# 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-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-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" + +# 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" + +# 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 +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" +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 + 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 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 + + $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++ + } + } + + 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 + } + + # 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 + 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 blob container + 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") + $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 + $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" + + # 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" + + # 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" -ForegroundColor Yellow + + # 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 + 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 complete YAML configuration + Write-Host "Creating container app with startup command..." -ForegroundColor Yellow + az containerapp create ` + --name $acaName ` + --resource-group $ResourceGroup ` + --yaml $containerYamlPath | 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: ********** (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" + 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 "" + Write-Host "To delete all resources, run:" -ForegroundColor Yellow + Write-Host " az group delete --name $ResourceGroup --yes --no-wait" + Write-Host "" + +} +catch { + Write-ErrorMessage "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 +} 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..377f202314 --- /dev/null +++ b/samples/azure/azure-container-instances-dab-starter.ps1 @@ -0,0 +1,694 @@ +<# +.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 + [Required] Azure subscription ID where all resources will be deployed. + You can find this with: az account show --query id -o tsv + +.PARAMETER ResourceGroup + [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 + [Optional] Azure region where resources will be deployed. Default: eastus + Supported regions: eastus, eastus2, westus, westus2, westus3, centralus, + northeurope, westeurope, uksouth, southeastasia + +.PARAMETER ResourcePrefix + [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 + [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 + [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 + [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 + [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 + [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 + [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 + [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 + $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 + +.EXAMPLE + .\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 -SubscriptionId "abc123" -ResourceGroup "rg-dab" -SqlAdminPassword $password -ContainerCpu 4 -ContainerMemory 8 + Deploys with high-performance CPU and memory configuration + +.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=$true)] + [string]$SubscriptionId, + + [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)] + [string]$ResourcePrefix, + + [Parameter(Mandatory=$true)] + [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, + + [Parameter(Mandatory=$false)] + [string]$DabConfigFile +) + +$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 +# ============================================================================ + +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-ErrorMessage { + 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-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-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 { + $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-ErrorMessage "Docker is not running or not responding. Please start Docker Desktop." + Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red + 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-ErrorMessage "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 Update-DabConfigFile { + param( + [string]$SourceConfigPath, + [string]$ConnectionString, + [string]$OutputPath + ) + + # 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" + } + + # 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" +} + +# ============================================================================ +# 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-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-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" + +# 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" + +# 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 +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 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 + + $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++ + } + } + + 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 + } + + # 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 + 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..." + 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 ` + --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 ` + --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: ********** (saved to file earlier)" + 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-ErrorMessage "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..dc74656688 100644 --- a/samples/azure/readme.md +++ b/samples/azure/readme.md @@ -1,7 +1,361 @@ -# 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 +# 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 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 +# 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 (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, 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) 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 + +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. Get your subscription ID +az account show --query id -o tsv + +# 3. Clone the repository +git clone https://github.com/Azure/data-api-builder.git +cd data-api-builder/samples/azure + +# 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 + +# Or with custom prefix: +.\azure-container-apps-dab-starter.ps1 ` + -SubscriptionId "" ` + -ResourceGroup "rg-dab-demo" ` + -ResourcePrefix "mydab" ` + -SqlAdminPassword $password + +# The script will: +# - Validate prerequisites (Azure CLI, Docker, sqlcmd) +# - Show a deployment summary +# - Ask for confirmation before proceeding +# - Create all Azure resources +# - Build and deploy DAB container +# - Load test data from DatabaseSchema-MsSql.sql +# - Display connection details and example commands +``` + +### Script Parameters + +#### Required Parameters (Both Scripts) +```powershell +-SubscriptionId # Azure subscription ID +-ResourceGroup # Resource group name +-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 +-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 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 Parameters +```powershell +-ContainerCpu # CPU cores: 1-4 (default: 1) +-ContainerMemory # Memory in GB: 0.5-16 (default: 1.5) +``` + +### Examples + +```powershell +# Prepare password for all examples +$password = ConvertTo-SecureString "" -AsPlainText -Force + +# 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" ` + -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 + +# 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" ` + -ResourcePrefix "mydab" ` + -SqlAdminPassword $password ` + -ContainerCpu 4 ` + -ContainerMemory 8 + +# Keep resources on failure for debugging +.\azure-container-apps-dab-starter.ps1 ` + -SubscriptionId "" ` + -ResourceGroup "rg-dab-debug" ` + -ResourcePrefix "debug" ` + -SqlAdminPassword $password ` + -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: +``` +========================================================================== + Deployment Summary +========================================================================== + +DAB Endpoints: + 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://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://mydab-aca-abc123.westus2.azurecontainerapps.io/graphql \ + -H 'Content-Type: application/json' \ + -d '{"query":"{books{items{id title year}}}"}' + +Cleanup: + az group delete --name rg-dab-aca --yes --no-wait +``` + +### 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.