Split-ALZ-Accelerator-exproj.txt
|
################################################################################ CURRENT DIRECTORY: /Users/gregc/devSandbox/github/acceleratorExtensoins/01-split-alz-accelerator/Split-ALZ-Accelerator/Split-ALZ-Accelerator ################################################################################ #################### DIRECTORY TREE #################### RELATIVE TO: /Users/gregc/devSandbox/github/acceleratorExtensoins/01-split-alz-accelerator/Split-ALZ-Accelerator/Split-ALZ-Accelerator . ├── private │ ├── Invoke-AccelCleanConnectivity.ps1 │ ├── Invoke-AccelCleanEmptyLocals.ps1 │ ├── Invoke-AccelCleanManagement.ps1 │ ├── Invoke-AccelMoveFiles.ps1 │ ├── Invoke-AccelOperation.ps1 │ ├── Invoke-AccelRefactorModules.ps1 │ ├── New-AccelBackup.ps1 │ ├── New-AccelDirectory.ps1 │ ├── New-AccelSymlink.ps1 │ ├── Remove-AccelLines.ps1 │ ├── Resolve-AccelPath.ps1 │ ├── Simplify-AccelStarterLocations.ps1 │ ├── Split-AccelLandingZoneAutoTfvars.ps1 │ ├── Trim-AccelConnectivityLocals.ps1 │ ├── Update-AccelModuleSources.ps1 │ └── Update-AccelProviders.ps1 ├── public │ └── Split-Accelerator.ps1 ├── Split-ALZ-Accelerator-exproj.txt ├── Split-ALZ-Accelerator.psd1 └── Split-ALZ-Accelerator.psm1 3 directories, 20 files #################### Split-ALZ-Accelerator/Split-ALZ-Accelerator.psm1 #################### # dot-source private helpers Get-ChildItem -Path (Join-Path $PSScriptRoot 'Private') -Filter '*.ps1' -File -ErrorAction SilentlyContinue | ForEach-Object { . $_.FullName } # dot-source public functions Get-ChildItem -Path (Join-Path $PSScriptRoot 'Public') -Filter '*.ps1' -File -ErrorAction SilentlyContinue | ForEach-Object { . $_.FullName } #################### Split-ALZ-Accelerator/Split-ALZ-Accelerator.psd1 #################### @{ RootModule = 'Split-ALZ-Accelerator.psm1' ModuleVersion = '0.3.1' GUID = 'b08e7c61-3c5a-46ce-8a7a-1d7f1f3d9b9f' Author = 'You' CompanyName = 'You' Description = 'Split an ALZ deployment into platform_* and refactor modules.' PowerShellVersion = '7.0' FunctionsToExport = @('Split-ALZ-Accelerator') PrivateData = @{ PSData = @{ Tags = @('ALZ','Terraform','Split','Accelerator') LicenseUri = 'https://opensource.org/licenses/MIT' } } } #################### Split-ALZ-Accelerator/public/Split-Accelerator.ps1 #################### function Split-ALZ-Accelerator { [CmdletBinding(SupportsShouldProcess, ConfirmImpact='Medium')] param( [Parameter(Mandatory)][string]$Path, [switch]$Force ) Write-Verbose "Split-ALZ-Accelerator starting at $(Get-Date -Format o)" if ($PSCmdlet.ShouldProcess($Path, "Backup and split ALZ into platform_*; refactor modules; clean configs; fix providers")) { # 1) Take a backup of the ALZ directory try { $backupPath = New-AccelBackup -Path $Path -Force:$Force -Confirm:$false -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference Write-Host "Backup created at: $backupPath" } catch { Write-Warning "Backup failed: $($_.Exception.Message)" # You can choose to 'return' here if you want to hard-fail when backup fails } # 2) Move files into platform_* directories, refactor modules, clean configs, fix providers, etc. $move = Invoke-AccelMoveFiles -Path $Path -Force:$Force -Confirm:$false -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference $refactor = Invoke-AccelRefactorModules -Path $Path -Force:$Force -Confirm:$false -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference $cleanConn = Invoke-AccelCleanConnectivity -Path $Path -Confirm:$false -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference $trimConn = Trim-AccelConnectivityLocals -Path $Path -Confirm:$false -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference $cleanMgmt = Invoke-AccelCleanManagement -Path $Path -Confirm:$false -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference $simplOK = Simplify-AccelStarterLocations -Path $Path -Confirm:$false -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference $fixProv = Invoke-AccelFixProviders -Path $Path -Confirm:$false -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference $cleanEmpty = Invoke-AccelCleanEmptyLocals -Path $Path -Confirm:$false -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference Write-Host ("Move: Moved={0} Copied={1} Deleted={2} Skipped={3}" -f $move.Moved,$move.Copied,$move.Deleted,$move.Skipped) Write-Host ("Refactor: RenamedModules={0} CustomModules={1} Rewritten={2} Symlinks={3} Skipped={4}" -f $refactor.ModulesRenamed,$refactor.CustomModulesReady,$refactor.FilesRewritten,$refactor.SymlinksCreated,$refactor.Skipped) Write-Host ("CleanConnectivity: FilesChanged={0}" -f $cleanConn) Write-Host ("TrimConnectivityLocals: Removed={0}" -f $trimConn) Write-Host ("CleanManagement: FilesChanged={0}" -f $cleanMgmt) Write-Host ("StarterLocationsSimplified: {0}" -f $simplOK) Write-Host ("FixProviders: ConnectivityUpdated={0} ManagementUpdated={1}" -f $fixProv.ConnectivityUpdated, $fixProv.ManagementUpdated) Write-Host ("CleanEmptyLocals: FilesChanged={0}" -f $cleanEmpty) return } } #################### Split-ALZ-Accelerator/private/Invoke-AccelCleanConnectivity.ps1 #################### function Invoke-AccelCleanConnectivity { [CmdletBinding(SupportsShouldProcess)] param([Parameter(Mandatory)][string]$Path) $root = Resolve-AccelPath -Path $Path $pc = Join-Path $root 'platform_connectivity' if (-not (Test-Path -LiteralPath $pc -PathType Container)) { return 0 } $patterns = @( 'var\.management_', '\bmodule\.management_resources\b' ) Remove-AccelLines -Directory $pc -RegexPatterns $patterns -Confirm:$false } #################### Split-ALZ-Accelerator/private/Invoke-AccelCleanManagement.ps1 #################### function Invoke-AccelCleanManagement { [CmdletBinding(SupportsShouldProcess)] param([Parameter(Mandatory)][string]$Path) $root = Resolve-AccelPath -Path $Path $pm = Join-Path $root 'platform_management' if (-not (Test-Path -LiteralPath $pm -PathType Container)) { return 0 } $patterns = @( '\bvar\.connectivity_type\b', '\bmodule\.resource_groups\b', '\bvar\.connectivity_resource_groups\b', '\bvar\.hub_and_spoke_networks_settings\b', '\bvar\.hub_virtual_networks\b', '\bvar\.virtual_wan_settings\b', '\bvar\.virtual_hubs\b', '\bvar\.connectivity_tags\b', '\bmodule\.hub_and_spoke_vnet\b', '\bmodule\.virtual_wan\b', '\bmodule\.config\.outputs\.(hub_and_spoke_networks_settings|hub_virtual_networks|virtual_wan_settings|virtual_hubs)\b' ) Remove-AccelLines -Directory $pm -RegexPatterns $patterns -Confirm:$false } #################### Split-ALZ-Accelerator/private/New-AccelBackup.ps1 #################### function New-AccelBackup { <# .SYNOPSIS Create a backup copy of the ALZ directory before we start moving/splitting. .DESCRIPTION Given a path to the ALZ repo root, create a sibling directory with a -bak, -bak1, -bak2... suffix and copy the entire tree there. Examples: C:\repos\alz-mgmt -> C:\repos\alz-mgmt-bak C:\repos\alz-mgmt -> C:\repos\alz-mgmt-bak1 (if -bak already exists) #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][string]$Path, [switch]$Force ) $root = Resolve-AccelPath -Path $Path $parent = Split-Path -Parent -Path $root $name = Split-Path -Leaf -Path $root # base candidate: <name>-bak, then <name>-bak1, -bak2, ... $suffix = '-bak' $candidateName = "$name$suffix" $candidatePath = Join-Path $parent $candidateName $i = 1 while (Test-Path -LiteralPath $candidatePath) { $candidateName = "{0}{1}{2}" -f $name, $suffix, $i $candidatePath = Join-Path $parent $candidateName $i++ } if ($PSCmdlet.ShouldProcess($root, "Backup to '$candidatePath'")) { # Copy the whole directory tree into the backup directory Copy-Item -LiteralPath $root -Destination $candidatePath -Recurse -Force:$Force -ErrorAction Stop } # Return backup path for logging if needed return $candidatePath } #################### Split-ALZ-Accelerator/private/Invoke-AccelRefactorModules.ps1 #################### function Invoke-AccelRefactorModules { <# .SYNOPSIS Phase 2: rename 'modules' → '_modules-accelerator', create '_modules-custom', rewrite module sources in .tf, and add .auto.tfvars symlinks. .EXAMPLE Invoke-AccelRefactorModules -Path '../alz-mgmt' -WhatIf -Verbose #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact='Medium')] param( [Parameter(Mandatory)][string]$Path, [switch]$Force ) $root = Resolve-AccelPath -Path $Path if (-not $PSCmdlet.ShouldProcess($root, "Refactor modules & add symlinks")) { return } $oldModules = Join-Path $root 'modules' $newModules = Join-Path $root '_modules-accelerator' $customMods = Join-Path $root '_modules-custom' $summary = [ordered]@{ ModulesRenamed = $false CustomModulesReady = $false FilesRewritten = 0 SymlinksCreated = 0 Skipped = 0 } try { $res = Invoke-AccelOperation -Action Move -Source $oldModules -Destination $newModules -Force:$Force -Confirm:$false if ($res -eq 'Moved' -or (Test-Path $newModules)) { $summary.ModulesRenamed = $true } else { $summary.Skipped++ } } catch { Write-Warning "Rename modules failed: $($_.Exception.Message)"; $summary.Skipped++ } try { New-AccelDirectory -Path $customMods -Confirm:$false | Out-Null $summary.CustomModulesReady = $true } catch { Write-Warning "Create _modules-custom failed: $($_.Exception.Message)"; $summary.Skipped++ } try { $rewritten = Update-AccelModuleSources -Root $root -Confirm:$false $summary.FilesRewritten = $rewritten } catch { Write-Warning "Update module sources failed: $($_.Exception.Message)"; $summary.Skipped++ } try { Split-AccelLandingZoneAutoTfvars -Path $root -Force:$Force -Confirm:$false -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference } catch { Write-Warning "Split platform-landing-zone.auto.tfvars failed: $($_.Exception.Message)" $summary.Skipped++ } $sharedTfvarsName = 'platform.shared.auto.tfvars' $platformDirs = @('platform_connectivity','platform_management') | ForEach-Object { Join-Path $root $_ } foreach ($pd in $platformDirs) { try { if (-not (Test-Path -LiteralPath $pd -PathType Container)) { Write-Verbose "Skip symlink, missing dir: $pd" $summary.Skipped++ continue } $link = Join-Path $pd $sharedTfvarsName $target = (Join-Path '..' $sharedTfvarsName) # relative target from inside platform_* to root New-AccelSymlink -LinkPath $link -TargetPath $target -Force:$Force -Confirm:$false | Out-Null $summary.SymlinksCreated++ } catch { Write-Warning "Symlink create failed for '$pd': $($_.Exception.Message)" $summary.Skipped++ } } [pscustomobject]$summary } #################### Split-ALZ-Accelerator/private/Update-AccelProviders.ps1 #################### function Update-AccelProviderInFile { <# .SYNOPSIS Ensure provider "azurerm" block contains the desired subscription_id assignment. If a subscription_id line exists, it is replaced; otherwise it's inserted after resource_provider_registrations (or at top of block if not found). #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][string]$FilePath, [Parameter(Mandatory)][string]$SubscriptionKey # e.g. 'connectivity' or 'management' ) if (-not (Test-Path -LiteralPath $FilePath -PathType Leaf)) { return $false } $content = Get-Content -LiteralPath $FilePath -Raw -ErrorAction Stop $nl = ($content -match "`r`n") ? "`r`n" : "`n" # find start of provider "azurerm" { $m = [regex]::Match($content, 'provider\s+"azurerm"\s*\{', 'IgnoreCase') if (-not $m.Success) { return $false } # find the opening brace and match to its closing brace using simple brace counting $openIdx = $content.IndexOf('{', $m.Index) if ($openIdx -lt 0) { return $false } $i = $openIdx $depth = 0 do { $ch = $content[$i] if ($ch -eq '{') { $depth++ } elseif ($ch -eq '}') { $depth-- } $i++ } while ($i -lt $content.Length -and $depth -gt 0) if ($depth -ne 0) { Write-Warning "Unbalanced braces in $FilePath provider block"; return $false } $blockStart = $m.Index $blockEnd = $i - 1 $block = $content.Substring($blockStart, $blockEnd - $blockStart + 1) # derive an indent from an existing property line, fall back to two spaces $indentMatch = [regex]::Match($block, "^\s+[A-Za-z_]+\s*=", 'Multiline') $indent = if ($indentMatch.Success) { ([regex]::Match($indentMatch.Value, '^\s+')).Value } else { ' ' } $desiredLine = $indent + 'subscription_id = var.subscription_ids["' + $SubscriptionKey + '"]' $blockNew = $block # replace existing subscription_id line if present $rxSubLine = [regex]::new('^\s*subscription_id\s*=\s*.*$', [System.Text.RegularExpressions.RegexOptions]::Multiline) if ($rxSubLine.IsMatch($blockNew)) { $blockNew = $rxSubLine.Replace($blockNew, [System.Text.RegularExpressions.MatchEvaluator]{ param($mm) $desiredLine }) } else { # insert after resource_provider_registrations line if present $rxRpr = [regex]::new('^(\s*resource_provider_registrations\s*=\s*".*")\s*$', 'Multiline') if ($rxRpr.IsMatch($blockNew)) { $blockNew = $rxRpr.Replace($blockNew, { param($mm) $mm.Groups[1].Value + $nl + $desiredLine }, 1) } else { # otherwise, insert just after opening brace $insPos = $blockNew.IndexOf('{') + 1 $blockNew = $blockNew.Substring(0,$insPos) + $nl + $desiredLine + $blockNew.Substring($insPos) } } if ($blockNew -ne $block) { $newContent = $content.Substring(0,$blockStart) + $blockNew + $content.Substring($blockEnd+1) if ($PSCmdlet.ShouldProcess($FilePath, "Set provider azurerm subscription_id = var.subscription_ids[`"$SubscriptionKey`"]")) { Set-Content -LiteralPath $FilePath -Value $newContent -Encoding UTF8 -ErrorAction Stop return $true } } return $false } function Invoke-AccelFixProviders { <# .SYNOPSIS Apply provider "azurerm" subscription_id fix to both platform_* terraform.tf files. #> [CmdletBinding(SupportsShouldProcess)] param([Parameter(Mandatory)][string]$Path) $root = Resolve-AccelPath -Path $Path $pcFile = Join-Path (Join-Path $root 'platform_connectivity') 'terraform.tf' $pmFile = Join-Path (Join-Path $root 'platform_management') 'terraform.tf' $c = $false $m = $false try { $c = Update-AccelProviderInFile -FilePath $pcFile -SubscriptionKey 'connectivity' -Confirm:$false } catch { Write-Warning "Fix provider (connectivity) failed: $($_.Exception.Message)" } try { $m = Update-AccelProviderInFile -FilePath $pmFile -SubscriptionKey 'management' -Confirm:$false } catch { Write-Warning "Fix provider (management) failed: $($_.Exception.Message)" } # return simple tuple-like object [pscustomobject]@{ ConnectivityUpdated = $c; ManagementUpdated = $m } } #################### Split-ALZ-Accelerator/private/Invoke-AccelCleanEmptyLocals.ps1 #################### function Invoke-AccelCleanEmptyLocals { <# .SYNOPSIS Removes empty Terraform locals blocks (`locals {}`) from platform_* directories. Works even if block spans multiple lines or has only whitespace inside. #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][string]$Path ) $root = Resolve-AccelPath -Path $Path $targets = @('platform_connectivity','platform_management') | ForEach-Object { Join-Path $root $_ } $rxEmptyLocals = [regex]::new( 'locals\s*\{\s*\}', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase ) $rxMultiline = [regex]::new( 'locals\s*\{(?:\s|\r|\n)*\}', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase ) $changed = 0 foreach ($dir in $targets) { if (-not (Test-Path -LiteralPath $dir -PathType Container)) { continue } $files = Get-ChildItem -Path $dir -Recurse -Filter '*.tf' -File -ErrorAction SilentlyContinue foreach ($f in $files) { try { $content = Get-Content -LiteralPath $f.FullName -Raw -ErrorAction Stop $newContent = $content # Replace both compact and multiline empty locals $newContent = $rxEmptyLocals.Replace($newContent, '') $newContent = $rxMultiline.Replace($newContent, '') if ($newContent -ne $content) { if ($PSCmdlet.ShouldProcess($f.FullName, 'Remove empty locals blocks')) { Set-Content -LiteralPath $f.FullName -Value $newContent -Encoding UTF8 -ErrorAction Stop $changed++ } } } catch { Write-Warning "Failed to clean empty locals in '$($f.FullName)': $($_.Exception.Message)" } } } return $changed } #################### Split-ALZ-Accelerator/private/Resolve-AccelPath.ps1 #################### function Resolve-AccelPath { [CmdletBinding()] param( [Parameter(Mandatory)][string]$Path ) $resolved = Resolve-Path -LiteralPath $Path -ErrorAction Stop if (-not (Test-Path -LiteralPath $resolved -PathType Container)) { throw "Path '$Path' exists but is not a directory." } return $resolved.ProviderPath } #################### Split-ALZ-Accelerator/private/New-AccelSymlink.ps1 #################### function New-AccelSymlink { <# .SYNOPSIS Create/ensure a symbolic link. If a file/link exists, overwrite only with -Force. #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][string]$LinkPath, [Parameter(Mandatory)][string]$TargetPath, [switch]$Force ) $dir = Split-Path -Parent -Path $LinkPath if ($dir) { New-AccelDirectory -Path $dir -Confirm:$false | Out-Null } if (Test-Path -LiteralPath $LinkPath) { if ($Force) { if ($PSCmdlet.ShouldProcess($LinkPath, "Replace existing with symlink → $TargetPath")) { Remove-Item -LiteralPath $LinkPath -Force -ErrorAction SilentlyContinue } } else { Write-Verbose "Link already exists, skipping: $LinkPath" return $true } } if ($PSCmdlet.ShouldProcess($LinkPath, "Create symlink → $TargetPath")) { New-Item -ItemType SymbolicLink -Path $LinkPath -Target $TargetPath | Out-Null } return $true } #################### Split-ALZ-Accelerator/private/Trim-AccelConnectivityLocals.ps1 #################### function Trim-AccelConnectivityLocals { <# .SYNOPSIS In platform_connectivity/locals.tf, remove assignments like 'management_group_settings = merge(...)' and 'management_resource_settings = merge(...)' (entire assignment, robust to formatting). #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][string]$Path ) $root = Resolve-AccelPath -Path $Path $file = Join-Path (Join-Path $root 'platform_connectivity') 'locals.tf' if (-not (Test-Path -LiteralPath $file -PathType Leaf)) { return 0 } try { $text = Get-Content -LiteralPath $file -Raw -ErrorAction Stop # targets we want to remove if they appear as "<name> = merge(" $targets = @('management_group_settings','management_resource_settings') $removed = 0 foreach ($name in $targets) { # regex to find start of "<name> = merge(" $pat = [System.Text.RegularExpressions.Regex]::new( [Regex]::Escape($name) + '\s*=\s*merge\s*\(', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase ) $matches = $pat.Matches($text) if ($matches.Count -eq 0) { continue } # remove from end to start so indices don’t shift for ($mi = $matches.Count - 1; $mi -ge 0; $mi--) { $m = $matches[$mi] $startIdx = $m.Index # find the matching closing ')' for this merge( $parenStart = $text.IndexOf('(', $m.Index) if ($parenStart -lt 0) { continue } $i = $parenStart + 1 $depth = 1 while ($i -lt $text.Length -and $depth -gt 0) { $ch = $text[$i] if ($ch -eq '(') { $depth++ } elseif ($ch -eq ')') { $depth-- } $i++ } if ($depth -ne 0) { continue } # unbalanced, skip safely $endIdx = $i # just after the closing ')' # swallow any trailing commas/whitespace/newlines after the merge(...) while ($endIdx -lt $text.Length) { $c = [char]$text[$endIdx] if ($c -eq ',' -or $c -eq ' ' -or $c -eq "`t" -or $c -eq "`r" -or $c -eq "`n") { $endIdx++ } else { break } } # expand start to beginning of its line $lineStart = $startIdx while ($lineStart -gt 0) { $prev = [char]$text[$lineStart - 1] if ($prev -eq "`n" -or $prev -eq "`r") { break } $lineStart-- } # remove the slice $text = $text.Remove($lineStart, $endIdx - $lineStart) $removed++ } } if ($removed -gt 0) { if ($PSCmdlet.ShouldProcess($file, "Trim $removed merge assignment(s) in locals.tf")) { Set-Content -LiteralPath $file -Value $text -Encoding UTF8 -ErrorAction Stop } } return $removed } catch { Write-Warning "Trimming connectivity locals failed for '$file': $($_.Exception.Message)" return 0 } } #################### Split-ALZ-Accelerator/private/New-AccelDirectory.ps1 #################### function New-AccelDirectory { [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][string]$Path ) if (Test-Path -LiteralPath $Path -PathType Container) { return $true } if ($PSCmdlet.ShouldProcess($Path, 'Create directory')) { New-Item -ItemType Directory -Path $Path -Force | Out-Null } return $true } #################### Split-ALZ-Accelerator/private/Invoke-AccelOperation.ps1 #################### function Invoke-AccelOperation { <# .SYNOPSIS Executes a single file system operation (Move/Copy/Delete/DeleteDir). #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][ValidateSet('Move','Copy','Delete','DeleteDir')] [string]$Action, [Parameter()][string]$Source, # required for Move/Copy/Delete [Parameter()][string]$Destination, # required for Move/Copy [switch]$Force ) switch ($Action) { 'Move' { if (-not (Test-Path -LiteralPath $Source)) { Write-Verbose "Skip move (missing): $Source"; return 'Skipped' } $destDir = Split-Path -Parent -Path $Destination if ($destDir) { New-AccelDirectory -Path $destDir | Out-Null } if ($PSCmdlet.ShouldProcess("$Source", "Move to $Destination")) { if (Test-Path -LiteralPath $Destination) { if ($Force) { Remove-Item -LiteralPath $Destination -Recurse -Force -ErrorAction Stop } else { Write-Warning "Destination exists, skipping: $Destination"; return 'Skipped' } } Move-Item -LiteralPath $Source -Destination $Destination -Force -ErrorAction Stop return 'Moved' } } 'Copy' { if (-not (Test-Path -LiteralPath $Source)) { Write-Verbose "Skip copy (missing): $Source"; return 'Skipped' } $destDir = Split-Path -Parent -Path $Destination if ($destDir) { New-AccelDirectory -Path $destDir | Out-Null } if ($PSCmdlet.ShouldProcess("$Source", "Copy to $Destination")) { if (Test-Path -LiteralPath $Destination) { if ($Force) { Remove-Item -LiteralPath $Destination -Recurse -Force -ErrorAction Stop } else { Write-Verbose "Destination exists; leaving as-is: $Destination"; return 'Skipped' } } Copy-Item -LiteralPath $Source -Destination $Destination -Recurse -Force -ErrorAction Stop return 'Copied' } } 'Delete' { if (-not (Test-Path -LiteralPath $Source)) { Write-Verbose "Skip delete (missing): $Source"; return 'Skipped' } if ($PSCmdlet.ShouldProcess("$Source", 'Delete file')) { Remove-Item -LiteralPath $Source -Force -ErrorAction Stop return 'Deleted' } } 'DeleteDir' { if (-not (Test-Path -LiteralPath $Source)) { Write-Verbose "Skip delete dir (missing): $Source"; return 'Skipped' } if ($PSCmdlet.ShouldProcess("$Source", 'Delete directory')) { Remove-Item -LiteralPath $Source -Recurse -Force -ErrorAction Stop return 'Deleted' } } } } #################### Split-ALZ-Accelerator/private/Update-AccelModuleSources.ps1 #################### function Update-AccelModuleSources { <# .SYNOPSIS Rewrite Terraform module sources from ./modules or ../modules to the correct relative path to _modules-accelerator, without stray quotes or escapes. #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][string]$Root, [string]$ModulesNewName = '_modules-accelerator' ) $rootPath = Resolve-AccelPath -Path $Root $modulesNew = Join-Path $rootPath $ModulesNewName # all .tf files except under _modules-accelerator or _modules-custom $tfFiles = Get-ChildItem -Path $rootPath -Recurse -Filter '*.tf' -File -ErrorAction SilentlyContinue | Where-Object { $_.FullName -notlike (Join-Path $modulesNew '*') -and $_.FullName -notlike (Join-Path $rootPath '_modules-custom*') } $changed = 0 foreach ($file in $tfFiles) { try { $content = Get-Content -LiteralPath $file.FullName -Raw -ErrorAction Stop # compute correct relative path to _modules-accelerator from this file's folder $rel = [System.IO.Path]::GetRelativePath($file.Directory.FullName, $modulesNew).Replace('\','/') if (-not $rel.EndsWith('/')) { $rel = "$rel/" } $newContent = $content # 1) Replace ONLY the prefix inside the quotes; keep the trailing module name # e.g. source = "./modules/config-templating" -> source = "../_modules-accelerator/config-templating" $patternPrefix = '(?<=\bsource\s*=\s*")\s*(?:\./|\.\./)modules/?' $newContent = [regex]::Replace($newContent, $patternPrefix, $rel) # 2) Fix any previously broken double-quote splits, e.g. # source = "../_modules-accelerator/"config-templating" $relEsc = [regex]::Escape($rel) $patternFixQuotes = '(?<=\bsource\s*=\s*")(' + $relEsc + ')"\s*([^"]+)"' $newContent = [regex]::Replace($newContent, $patternFixQuotes, '$1$2"') # 3) Clean up accidental backslash-escaped segments from earlier runs, e.g. \.\./_modules-accelerator/ $newContent = $newContent -replace '(?<=\bsource\s*=\s*")\\\.\./','../' # Remove stray backslashes just before our prefix or ../ (conservative lookahead) $newContent = $newContent -replace '(?<=\bsource\s*=\s*")\\(?=(\.\./|[A-Za-z0-9_\-]+/))','' if ($newContent -ne $content) { if ($PSCmdlet.ShouldProcess($file.FullName, "Rewrite module sources → $rel")) { Set-Content -LiteralPath $file.FullName -Value $newContent -Encoding UTF8 -ErrorAction Stop $changed++ } } } catch { Write-Warning "Module source rewrite failed for '$($file.FullName)': $($_.Exception.Message)" continue } } return $changed } #################### Split-ALZ-Accelerator/private/Split-AccelLandingZoneAutoTfvars.ps1 #################### function Split-AccelLandingZoneAutoTfvars { <# .SYNOPSIS Split platform-landing-zone.auto.tfvars into: - root/platform.shared.auto.tfvars (custom_replacements + enable_telemetry) - platform_management/management.auto.tfvars (tags + management_* settings) - platform_connectivity/connectivity.auto.tfvars (tags + connectivity_* settings) then delete platform-landing-zone.auto.tfvars. .NOTES - Idempotent: if all target files already exist, it does nothing (unless -Force). - Conservative parsing using brace counting; assumes ALZ's basic layout. #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][string]$Path, [switch]$Force ) $root = Resolve-AccelPath -Path $Path $tfvarsRoot = Join-Path $root 'platform-landing-zone.auto.tfvars' if (-not (Test-Path -LiteralPath $tfvarsRoot -PathType Leaf)) { Write-Verbose "No platform-landing-zone.auto.tfvars found at $tfvarsRoot; skipping split." return } $pmDir = Join-Path $root 'platform_management' $pcDir = Join-Path $root 'platform_connectivity' $mgmtFile = Join-Path $pmDir 'management.auto.tfvars' $connFile = Join-Path $pcDir 'connectivity.auto.tfvars' $sharedFile = Join-Path $root 'platform.shared.auto.tfvars' # If we've already split and not forcing, bail out if ((Test-Path -LiteralPath $mgmtFile) -and (Test-Path -LiteralPath $connFile) -and (Test-Path -LiteralPath $sharedFile) -and -not $Force) { Write-Verbose "management/ connectivity / shared .auto.tfvars already exist; skipping tfvars split." return } if (-not (Test-Path -LiteralPath $pmDir -PathType Container) -or -not (Test-Path -LiteralPath $pcDir -PathType Container)) { Write-Verbose "platform_* directories not found; skipping tfvars split." return } $text = Get-Content -LiteralPath $tfvarsRoot -Raw -ErrorAction Stop $nl = ($text -match "`r`n") ? "`r`n" : "`n" # Small helpers for parsing from the original text function Get-HclObjectBlock { param( [string]$Name ) $idx = $text.IndexOf($Name) if ($idx -lt 0) { return $null } # find '=' then '{' $eqIdx = $text.IndexOf('=', $idx) if ($eqIdx -lt 0) { return $null } $openIdx = $text.IndexOf('{', $eqIdx) if ($openIdx -lt 0) { return $null } $i = $openIdx $depth = 0 $closeIdx = -1 while ($i -lt $text.Length) { $ch = $text[$i] if ($ch -eq '{') { $depth++ } elseif ($ch -eq '}') { $depth-- if ($depth -eq 0) { $closeIdx = $i break } } $i++ } if ($closeIdx -lt 0) { return $null } # include comments directly above, if any $start = $idx # go back to line start while ($start -gt 0 -and $text[$start - 1] -notin "`r","`n") { $start-- } # pull in a preceding /* ... */ comment if it ends immediately above $commentStart = $text.LastIndexOf("/*", $start - 1) $commentEnd = if ($commentStart -ge 0) { $text.IndexOf("*/", $commentStart) } else { -1 } if ($commentStart -ge 0 -and $commentEnd -ge 0 -and $commentEnd -lt $start) { # Only pull it in if there’s no blank line between comment and block $between = $text.Substring($commentEnd + 2, $start - ($commentEnd + 2)) if ($between.Trim() -eq '') { $start = $commentStart } } $end = $closeIdx + 1 # eat trailing whitespace while ($end -lt $text.Length -and $text[$end] -in " ","`t","`r","`n") { $end++ } [pscustomobject]@{ Name = $Name Start = $start End = $end Text = $text.Substring($start, $end - $start) } } function Get-SimpleAssignmentLine { param( [string]$Name ) $idx = $text.IndexOf($Name) if ($idx -lt 0) { return $null } # go to start-of-line $start = $idx while ($start -gt 0 -and $text[$start-1] -notin "`r","`n") { $start-- } # go to end-of-line $end = $idx while ($end -lt $text.Length -and $text[$end] -notin "`r","`n") { $end++ } if ($end -lt $text.Length) { $end++ } # include newline [pscustomobject]@{ Name = $Name Start = $start End = $end Text = $text.Substring($start, $end - $start) } } # Extract the pieces we care about $tagsBlock = Get-HclObjectBlock -Name 'tags' $mgmtResBlock = Get-HclObjectBlock -Name 'management_resource_settings' $mgmtGroupBlock = Get-HclObjectBlock -Name 'management_group_settings' $connTypeLine = Get-SimpleAssignmentLine -Name 'connectivity_type' $connRgsBlock = Get-HclObjectBlock -Name 'connectivity_resource_groups' $virtualHubsBlock = Get-HclObjectBlock -Name 'virtual_hubs' $customReplBlock = Get-HclObjectBlock -Name 'custom_replacements' $enableTelemetryLine = Get-SimpleAssignmentLine -Name 'enable_telemetry' # Build management.auto.tfvars $mgmtParts = @() if ($tagsBlock) { $mgmtParts += $tagsBlock.Text.TrimEnd() } if ($mgmtResBlock) { $mgmtParts += $mgmtResBlock.Text.TrimEnd() } if ($mgmtGroupBlock) { $mgmtParts += $mgmtGroupBlock.Text.TrimEnd() } $mgmtContent = if ($mgmtParts.Count -gt 0) { ($mgmtParts -join (@($nl,$nl))) + $nl } else { '' } # Build connectivity.auto.tfvars $connParts = @() if ($tagsBlock) { $connParts += $tagsBlock.Text.TrimEnd() } if ($connTypeLine) { $connParts += $connTypeLine.Text.TrimEnd() } if ($connRgsBlock) { $connParts += $connRgsBlock.Text.TrimEnd() } if ($virtualHubsBlock) { $connParts += $virtualHubsBlock.Text.TrimEnd() } $connContent = if ($connParts.Count -gt 0) { ($connParts -join (@($nl,$nl))) + $nl } else { '' } # Build platform.shared.auto.tfvars (shared bits only) $sharedParts = @() if ($customReplBlock) { $sharedParts += $customReplBlock.Text.TrimEnd() } if ($enableTelemetryLine) { $sharedParts += $enableTelemetryLine.Text.TrimEnd() } $sharedContent = if ($sharedParts.Count -gt 0) { ($sharedParts -join (@($nl,$nl))) + $nl } else { '' } # Write files if ($PSCmdlet.ShouldProcess($mgmtFile, "Write management.auto.tfvars")) { New-AccelDirectory -Path $pmDir -Confirm:$false | Out-Null Set-Content -LiteralPath $mgmtFile -Value $mgmtContent -Encoding UTF8 -ErrorAction Stop } if ($PSCmdlet.ShouldProcess($connFile, "Write connectivity.auto.tfvars")) { New-AccelDirectory -Path $pcDir -Confirm:$false | Out-Null Set-Content -LiteralPath $connFile -Value $connContent -Encoding UTF8 -ErrorAction Stop } if ($PSCmdlet.ShouldProcess($sharedFile, "Write platform.shared.auto.tfvars")) { Set-Content -LiteralPath $sharedFile -Value $sharedContent -Encoding UTF8 -ErrorAction Stop } # Finally, delete the original source file if ($PSCmdlet.ShouldProcess($tfvarsRoot, 'Delete original platform-landing-zone.auto.tfvars')) { Remove-Item -LiteralPath $tfvarsRoot -Force -ErrorAction Stop } } #################### Split-ALZ-Accelerator/private/Remove-AccelLines.ps1 #################### function Remove-AccelLines { <# .SYNOPSIS Remove entire lines from all *.tf files under a directory if they match any given regex patterns. #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][string]$Directory, [Parameter(Mandatory)][string[]]$RegexPatterns ) $dirPath = Resolve-AccelPath -Path $Directory if (-not (Test-Path -LiteralPath $dirPath -PathType Container)) { return 0 } $files = Get-ChildItem -Path $dirPath -Recurse -Filter '*.tf' -File -ErrorAction SilentlyContinue if (-not $files) { return 0 } # combine to single case-insensitive regex $rx = [regex]::new('(' + ($RegexPatterns -join '|') + ')', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) $changedCount = 0 foreach ($f in $files) { try { $raw = Get-Content -LiteralPath $f.FullName -Raw -ErrorAction Stop $eol = ($raw -match "`r`n") ? "`r`n" : "`n" $lines = $raw -split "`r?`n" $modified = $false $kept = foreach ($ln in $lines) { if ($rx.IsMatch($ln)) { $modified = $true; continue } $ln } if ($modified) { $new = [string]::Join($eol, $kept) if ($PSCmdlet.ShouldProcess($f.FullName, "Remove lines matching patterns")) { Set-Content -LiteralPath $f.FullName -Value $new -Encoding UTF8 -ErrorAction Stop $changedCount++ } } } catch { Write-Warning "Line cleanup failed for '$($f.FullName)': $($_.Exception.Message)" continue } } return $changedCount } #################### Split-ALZ-Accelerator/private/Invoke-AccelMoveFiles.ps1 #################### function Invoke-AccelMoveFiles { [CmdletBinding(SupportsShouldProcess, ConfirmImpact='Medium')] param( [Parameter(Mandatory)][string]$Path, [switch]$Force ) $root = Resolve-AccelPath -Path $Path $pc = Join-Path $root 'platform_connectivity' $pm = Join-Path $root 'platform_management' if (-not $PSCmdlet.ShouldProcess($root, "Move/copy/delete files into platform_*")) { return } New-AccelDirectory -Path $pc -Confirm:$false | Out-Null New-AccelDirectory -Path $pm -Confirm:$false | Out-Null $ops = @() $ops += @{ Action='Move'; Source=(Join-Path $root 'lib'); Destination=(Join-Path $pm 'lib') } $ops += @{ Action='Move'; Source=(Join-Path $root 'main.connectivity.hub.and.spoke.virtual.network.tf'); Destination=(Join-Path $pc 'main.connectivity.hub.and.spoke.virtual.network.tf') } $ops += @{ Action='Move'; Source=(Join-Path $root 'main.connectivity.virtual.wan.tf'); Destination=(Join-Path $pc 'main.connectivity.virtual.wan.tf') } $ops += @{ Action='Move'; Source=(Join-Path $root 'main.management.tf'); Destination=(Join-Path $pm 'main.management.tf') } $ops += @{ Action='Move'; Source=(Join-Path $root 'main.resource.groups.tf'); Destination=(Join-Path $pc 'main.resource.groups.tf') } $ops += @{ Action='Move'; Source=(Join-Path $root 'outputs.tf'); Destination=(Join-Path $pc 'outputs.tf') } Get-ChildItem -Path (Join-Path $root 'variables.connectivity*') -File -ErrorAction SilentlyContinue | ForEach-Object { $ops += @{ Action='Move'; Source=$_.FullName; Destination=(Join-Path $pc $_.Name) } } $ops += @{ Action='Move'; Source=(Join-Path $root 'variables.management.tf'); Destination=(Join-Path $pm 'variables.management.tf') } $ops += @{ Action='Copy'; Source=(Join-Path $root 'terraform.tfvars.json'); Destination=(Join-Path $pc 'terraform.tfvars.json') } $ops += @{ Action='Copy'; Source=(Join-Path $root 'terraform.tfvars.json'); Destination=(Join-Path $pm 'terraform.tfvars.json') } $ops += @{ Action='Copy'; Source=(Join-Path $root 'terraform.tf'); Destination=(Join-Path $pc 'terraform.tf') } $ops += @{ Action='Copy'; Source=(Join-Path $root 'terraform.tf'); Destination=(Join-Path $pm 'terraform.tf') } $ops += @{ Action='Copy'; Source=(Join-Path $root 'main.config.tf'); Destination=(Join-Path $pc 'main.config.tf') } $ops += @{ Action='Copy'; Source=(Join-Path $root 'main.config.tf'); Destination=(Join-Path $pm 'main.config.tf') } $ops += @{ Action='Copy'; Source=(Join-Path $root 'locals.tf'); Destination=(Join-Path $pc 'locals.tf') } $ops += @{ Action='Copy'; Source=(Join-Path $root 'locals.tf'); Destination=(Join-Path $pm 'locals.tf') } $ops += @{ Action='Copy'; Source=(Join-Path $root 'variables.tf'); Destination=(Join-Path $pc 'variables.tf') } $ops += @{ Action='Copy'; Source=(Join-Path $root 'variables.tf'); Destination=(Join-Path $pm 'variables.tf') } $ops += @{ Action='Delete'; Source=(Join-Path $root 'README.md') } $ops += @{ Action='Delete'; Source=(Join-Path $root 'terraform.tfvars.json') } $ops += @{ Action='Delete'; Source=(Join-Path $root 'terraform.tf') } $ops += @{ Action='Delete'; Source=(Join-Path $root 'locals.tf') } $ops += @{ Action='Delete'; Source=(Join-Path $root 'variables.tf') } $ops += @{ Action='Delete'; Source=(Join-Path $root 'main.config.tf') } $ops += @{ Action='DeleteDir'; Source=(Join-Path $root 'scripts') } $summary = [ordered]@{Moved=0; Copied=0; Deleted=0; Skipped=0} foreach ($op in $ops) { try { $result = Invoke-AccelOperation @op -Force:$Force -Confirm:$false } catch { Write-Warning ("Failed {0} '{1}' -> '{2}': {3}" -f $op.Action,$op.Source,$op.Destination,$_.Exception.Message) $result = 'Skipped' } switch ($result) { 'Moved' { $summary.Moved++ } 'Copied' { $summary.Copied++ } 'Deleted' { $summary.Deleted++ } default { $summary.Skipped++ } } } if ($WhatIfPreference) { return [pscustomobject]@{ MovedPlanned = ($ops | Where-Object Action -eq 'Move').Count CopiedPlanned = ($ops | Where-Object Action -eq 'Copy').Count DeletedPlanned = ($ops | Where-Object { $_.Action -in 'Delete','DeleteDir' }).Count TotalPlanned = $ops.Count } } [pscustomobject]$summary } #################### Split-ALZ-Accelerator/private/Simplify-AccelStarterLocations.ps1 #################### function Simplify-AccelStarterLocations { <# .SYNOPSIS Replace the entire variable "starter_locations" block in platform_management/variables.tf with the simplified version (removing the connectivity-related validation). #> [CmdletBinding(SupportsShouldProcess)] param([Parameter(Mandatory)][string]$Path) $root = Resolve-AccelPath -Path $Path $file = Join-Path (Join-Path $root 'platform_management') 'variables.tf' if (-not (Test-Path -LiteralPath $file -PathType Leaf)) { return $false } try { $content = Get-Content -LiteralPath $file -Raw -ErrorAction Stop $match = [regex]::Match($content, 'variable\s+"starter_locations"\s*\{', 'IgnoreCase') if (-not $match.Success) { return $false } $i = $match.Index + $match.Length $depth = 1 while ($i -lt $content.Length) { $ch = $content[$i] if ($ch -eq '{') { $depth++ } elseif ($ch -eq '}') { $depth-- if ($depth -eq 0) { break } } $i++ } if ($depth -ne 0) { Write-Warning "Could not parse balanced braces for starter_locations in $file" return $false } $before = $content.Substring(0, $match.Index) $after = $content.Substring($i + 1) $nl = ($content -match "`r`n") ? "`r`n" : "`n" $replacement = @" variable "starter_locations" { type = list(string) description = "The default for Azure resources. (e.g 'uksouth')" validation { condition = length(var.starter_locations) > 0 error_message = "You must provide at least one starter location region." } } "@ -replace "`r?`n", $nl if ($PSCmdlet.ShouldProcess($file, 'Replace starter_locations validation block')) { Set-Content -LiteralPath $file -Value ($before + $replacement + $after) -Encoding UTF8 -ErrorAction Stop return $true } } catch { Write-Warning "Simplify starter_locations failed for '$file': $($_.Exception.Message)" } return $false } |