public/New-AzBootstrap.ps1
function New-AzBootstrap { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$TemplateRepoUrl, [Parameter(Mandatory)] [string]$TargetRepoName, [Parameter(Mandatory)] [string]$Location, # required but can optionally use environment variables [string]$ArmTenantId = $env:ARM_TENANT_ID, [string]$ArmSubscriptionId = $env:ARM_SUBSCRIPTION_ID, # optional [string]$Owner, # Optional, defaults to current user/org [string]$InitialEnvironmentName = "dev", [string]$ResourceGroupName = "rg-$TargetRepoName-$InitialEnvironmentName", [string]$ManagedIdentityName = "mi-$TargetRepoName-$InitialEnvironmentName", [string]$PlanEnvName = "${InitialEnvironmentName}-iac-plan", [string]$ApplyEnvName = "${InitialEnvironmentName}-iac-apply", [ValidateSet("public", "private", "internal")] [string]$Visibility = "public", [string]$TargetDirectory, # defaults to ".\$targetreponame" [string[]]$ApplyEnvironmentReviewers = @(), # Default reviewers for the Apply environment (empty array = no reviewers) [string]$ProtectedBranchName = "main", # Default branch to protect [bool]$RequirePR = $true, # Default: Require PR for protected branch [int]$RequiredReviewers = 0, # Default: Required reviewers for protected branch # add the branch ruleset defaults [string]$BranchRulesetName = "main", # Default branch ruleset name [string]$BranchTargetPattern = "main", # Default branch target pattern [int]$BranchRequiredApprovals = 1, # Default required approvals for branch ruleset [bool]$BranchDismissStaleReviews = $true, # Default: Dismiss stale reviews on push [bool]$BranchRequireCodeOwnerReview = $false, # Default: Require code owner review [bool]$BranchRequireLastPushApproval = $false, # Default: Require last push approval [bool]$BranchRequireThreadResolution = $false, # Default: Require thread resolution [string[]]$BranchAllowedMergeMethods = @("squash"), # Default allowed merge methods [bool]$BranchEnableCopilotReview = $true, # Default: Enable Copilot review [bool]$AddOwnerAsReviewer = $true # Default: Add owner as reviewer for the Apply environment ) #region: check target directory if (-not $TargetDirectory -or [string]::IsNullOrWhiteSpace($TargetDirectory)) { $TargetDirectory = Join-Path -Path (Get-Location) -ChildPath $TargetRepoName } if (Test-Path $TargetDirectory) { throw "Target directory '$TargetDirectory' already exists. Please specify a new directory." } #endregion #region: check CLI tools Write-Host "[az-bootstrap] Checking GitHub CLI authentication status..." if (-not (Test-GitHubCLI)) { throw "GitHub CLI is not authenticated. Please run 'gh auth login' to authenticate." } Write-Host "[az-bootstrap] Checking Az CLI authentication status..." if (-not (Test-AzCli -TenantId $ArmTenantId -SubscriptionId $ArmSubscriptionId)) { throw "Az CLI is not authenticated. Please run 'az login' to authenticate." } #endregion # GitHub repo $ownerArg = if ($Owner) { "--owner $Owner" } else { "" } $visibilityArg = switch ($Visibility) { "private" { "--private" } "internal" { "--internal" } Default { "--public" } } Write-Host "[az-bootstrap] Creating new GitHub repo '$TargetRepoName' from template: $TemplateRepoUrl" $cmd = "gh repo create $TargetRepoName --template $TemplateRepoUrl $visibilityArg $ownerArg" Invoke-Expression $cmd if ($LASTEXITCODE -ne 0) { throw "Failed to create new GitHub repository from template." } $actualOwner = if ($Owner) { $Owner } else { # Try to get the current user/org from gh CLI $user = gh auth status --show-token 2>$null | Select-String 'Logged in to github.com account (.*) \(' | ForEach-Object { $_.Matches.Groups[1].Value } if ($user) { $user } else { throw "Could not determine GitHub owner. Please specify -Owner." } } $repoUrl = "https://github.com/$actualOwner/$TargetRepoName.git" Write-Host "[az-bootstrap] Cloning new repo '$actualOwner/$TargetRepoName' to $TargetDirectory" git clone $repoUrl $TargetDirectory if ($LASTEXITCODE -ne 0) { throw "Failed to clone new repository from $repoUrl." } Push-Location $TargetDirectory try { $repoInfo = Get-GitHubRepositoryInfo if (-not $repoInfo) { throw "Could not determine repository information from git remote or overrides." } New-GitHubBranchRuleset -Owner $repoInfo.Owner ` -Repo $repoInfo.Repo ` -RulesetName "main" ` -TargetPattern $ProtectedBranchName ` -RequiredApprovals $RequiredReviewers ` -DismissStaleReviews $BranchDismissStaleReviews ` -RequireCodeOwnerReview $BranchRequireCodeOwnerReview ` -RequireLastPushApproval $BranchRequireLastPushApproval ` -RequireThreadResolution $BranchRequireThreadResolution ` -AllowedMergeMethods $BranchAllowedMergeMethods ` -EnableCopilotReview $BranchEnableCopilotReview # GitHub environment setup $DeploymentEnv = Add-Environment ` -EnvironmentName $InitialEnvironmentName ` -ResourceGroupName $ResourceGroupName ` -Location $Location ` -ManagedIdentityName $ManagedIdentityName ` -ArmTenantId $ArmTenantId ` -ArmSubscriptionId $ArmSubscriptionId ` -Owner $repoInfo.Owner ` -Repo $repoInfo.Repo ` -PlanEnvName $PlanEnvName ` -ApplyEnvName $ApplyEnvName ` -ApplyEnvironmentReviewers $ApplyEnvironmentReviewers ` -ApplyEnvironmentTeamReviewers $ApplyEnvironmentTeamReviewers ` -AddOwnerAsReviewer $AddOwnerAsReviewer Write-Host "[az-bootstrap] $($DeploymentEnv.EnvironmentName) environment created." } finally { Pop-Location } Write-Host "[az-bootstrap] Bootstrap complete. 🎉" } Export-ModuleMember -Function New-AzBootstrap |