private/New-AzBicepDeployment.ps1
function New-AzBicepDeployment { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$EnvironmentName, [Parameter(Mandatory)] [string]$ResourceGroupName, [Parameter(Mandatory)] [string]$Location, # Used for deployment metadata and as default for resources [Parameter(Mandatory)] [string]$PlanManagedIdentityName, [Parameter(Mandatory)] [string]$ApplyManagedIdentityName, [Parameter(Mandatory)] [string]$GitHubOwner, [Parameter(Mandatory)] [string]$GitHubRepo, [Parameter(Mandatory)] [string]$PlanEnvName, [Parameter(Mandatory)] [string]$ApplyEnvName, [Parameter(Mandatory)] # Subscription ID is required for subscription-level deployments [string]$ArmSubscriptionId, [string]$TerraformStateStorageAccountName ) $bicepTemplateFile = Join-Path $PSScriptRoot '..' 'templates' 'environment-infra.bicep' if (-not (Test-Path $bicepTemplateFile)) { throw "Bicep template file not found at '$bicepTemplateFile'." } $resolvedBicepTemplateFile = Resolve-Path $bicepTemplateFile -ErrorAction Stop Write-Host "[az-bootstrap] This may take a few minutes, please wait..." $bicepParams = @{ resourceGroupName = $ResourceGroupName location = $Location planManagedIdentityName = $PlanManagedIdentityName applyManagedIdentityName = $ApplyManagedIdentityName gitHubOwner = $GitHubOwner.ToLower() gitHubRepo = $GitHubRepo.ToLower() gitHubPlanEnvironmentName = $PlanEnvName.ToLower() gitHubApplyEnvironmentName = $ApplyEnvName.ToLower() terraformStateStorageAccountName = $TerraformStateStorageAccountName } # Remove any parameters with $null values, as Bicep might error on `paramName=$null` $activeBicepParams = $bicepParams.GetEnumerator() | Where-Object { $_.Value -ne $null } | ForEach-Object { "$( $_.Name )=$( $_.Value )" } $stackName = "azbootstrap-stack-$($EnvironmentName)-$(Get-Date -Format 'yyyyMMddHHmmss')" $azCliArgs = @( 'stack', 'sub', 'create', '--name', $stackName, '--location', $Location, '--template-file', $resolvedBicepTemplateFile, '--action-on-unmanage', 'deleteResources', '--deny-settings-mode', 'none', '--parameters' ) $azCliArgs += $activeBicepParams Write-Host "[az-bootstrap] Creating Azure infrastructure via deployment stack '$stackName'..." Write-Verbose "[az-bootstrap] Executing: az $($azCliArgs -join ' ')" $stdoutfile = New-TemporaryFile $stderrfile = New-TemporaryFile $process = Start-Process "az" -ArgumentList $azCliArgs -Wait -NoNewWindow -PassThru -RedirectStandardOutput $stdoutfile -RedirectStandardError $stderrfile $stdout = Get-Content $stdoutfile -Raw $stderr = Get-Content $stderrfile -ErrorAction SilentlyContinue Remove-Item $stdoutfile, $stderrfile -ErrorAction SilentlyContinue if ($process.ExitCode -ne 0) { Write-Error "[az-bootstrap] Stack deployment failed for environment '$EnvironmentName'. Exit Code: $($process.ExitCode)" Write-Error "[az-bootstrap] Standard Error: $stderr" Write-Error "[az-bootstrap] Standard Output (may contain JSON error from Azure): $stdout" throw "Stack deployment for environment '$EnvironmentName' failed." } $deploymentOutput = $stdout | ConvertFrom-Json -ErrorAction SilentlyContinue if (-not $deploymentOutput -or -not $deploymentOutput.outputs) { Write-Error "[az-bootstrap] Stack deployment outputs not found or failed to parse. Raw STDOUT: $stdout" throw "Stack deployment for environment '$EnvironmentName' did not produce expected outputs." } $planManagedIdentityClientId = $null $applyManagedIdentityClientId = $null $planManagedIdentityClientId = $deploymentOutput.outputs.planManagedIdentityClientId.value if (-not $planManagedIdentityClientId) { throw "Failed to retrieve Primary Managed Identity Client ID from Bicep deployment for environment '$EnvironmentName'." } $applyManagedIdentityClientId = $deploymentOutput.outputs.applyManagedIdentityClientId.value if (-not $applyManagedIdentityClientId) { throw "Failed to retrieve Apply-Specific Managed Identity Client ID from Bicep deployment when CreateSeparateApplyMI was true for environment '$EnvironmentName'." } $duration = $deploymentOutput.duration try { $ts = [System.Xml.XmlConvert]::ToTimeSpan($duration) $friendlyDuration = "in {0}m {1}s." -f $ts.Minutes, $ts.Seconds } catch { $friendlyDuration = "." } Write-Host -NoNewline "`u{2713} " -ForegroundColor Green Write-Host "Bicep deployment for '$EnvironmentName' $($deploymentOutput.provisioningState)" $friendlyDuration Write-Verbose "[az-bootstrap] Plan MI Client ID: $planManagedIdentityClientId" Write-Verbose "[az-bootstrap] Apply MI Client ID: $applyManagedIdentityClientId" return [PSCustomObject]@{ PlanManagedIdentityClientId = $planManagedIdentityClientId ApplyManagedIdentityClientId = $applyManagedIdentityClientId } } |