Private/Get-AzLocalSideloadAuthMap.ps1
|
function Get-AzLocalSideloadAuthMap { <# .SYNOPSIS Parses the sideload auth-map CSV that maps the numeric UpdateAuthAccountId cluster tag to the Key Vault secrets + remoting settings used to stage a sideloaded solution update on an on-premises Azure Local cluster. .DESCRIPTION Private helper for the v0.8.7 on-prem sideloading automation feature. Reads a CSV with these columns: - UpdateAuthAccountId (REQUIRED) numeric account id, 1-3 digits (e.g. 001). Matches the per-cluster 'UpdateAuthAccountId' tag written by Step.2. - KeyVaultName (REQUIRED) Key Vault holding the AD credential. - UsernameSecretName (REQUIRED) KV secret holding the username (UPN 'user@contoso.com' recommended; 'DOMAIN\user' also accepted). - PasswordSecretName (REQUIRED) KV secret holding the password. - RemotingTargetFqdn (optional) explicit FQDN to remote to; when set it overrides the cluster-name + suffix resolution. - FqdnSuffix (optional) per-row DNS suffix appended to the cluster name (e.g. '.corp.contoso.com'). - AuthMechanism (optional) WinRM auth mechanism (Kerberos|Negotiate|Default). Defaults to Default at the remoting layer. - ImportSharePath (optional) override for the cluster infrastructure 'import' UNC share. Validation: - UpdateAuthAccountId must match ^\d{1,3}$ (numeric only). The raw (trimmed) string is used verbatim as the lookup key so it must match the tag value exactly. - KeyVaultName, UsernameSecretName, PasswordSecretName must be non-empty. - A DUPLICATE UpdateAuthAccountId is a HARD ERROR (the mapping would be ambiguous). This is a pre-requisite gate for the sideload pipeline. Returns a hashtable keyed by the trimmed UpdateAuthAccountId string, whose values are [PSCustomObject] rows. An empty CSV returns an empty hashtable. .PARAMETER Path Path to the auth-map CSV file. .OUTPUTS [hashtable] keyed by UpdateAuthAccountId -> [PSCustomObject]. #> [CmdletBinding()] [OutputType([hashtable])] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$Path ) if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { throw "Sideload auth-map CSV not found at '$Path'. Set SIDELOAD_AUTH_MAP_PATH (or pass -Path) to a CSV with columns UpdateAuthAccountId,KeyVaultName,UsernameSecretName,PasswordSecretName." } try { # Tolerate inline documentation: drop blank lines and '#'-prefixed # comment lines (Import-Csv has no native comment support) before # parsing. The real header is the first non-comment line. $rawLines = @(Get-Content -LiteralPath $Path -ErrorAction Stop) $dataLines = @($rawLines | Where-Object { -not [string]::IsNullOrWhiteSpace($_) -and ($_.TrimStart())[0] -ne '#' }) $rows = if ($dataLines.Count -gt 0) { @($dataLines | ConvertFrom-Csv) } else { @() } } catch { throw "Failed to read sideload auth-map CSV '$Path': $($_.Exception.Message)" } $authMap = @{} if ($rows.Count -eq 0) { return $authMap } # Column presence is validated from the first row's properties so a # misspelled header surfaces clearly instead of as silent $null fields. $firstRow = $rows[0] $required = @('UpdateAuthAccountId', 'KeyVaultName', 'UsernameSecretName', 'PasswordSecretName') $columns = @($firstRow.PSObject.Properties.Name) $missing = @($required | Where-Object { $columns -notcontains $_ }) if ($missing.Count -gt 0) { throw "Sideload auth-map CSV '$Path' is missing required column(s): $($missing -join ', '). Required columns: $($required -join ', ')." } $rowIndex = 0 foreach ($row in $rows) { $rowIndex++ $accountId = ([string]$row.UpdateAuthAccountId).Trim() if ([string]::IsNullOrWhiteSpace($accountId)) { throw "Sideload auth-map CSV '$Path' row $rowIndex has an empty UpdateAuthAccountId." } if ($accountId -notmatch '^\d{1,3}$') { throw "Sideload auth-map CSV '$Path' row $rowIndex has an invalid UpdateAuthAccountId '$accountId'. Must be numeric (1-3 digits, e.g. 001)." } if ($authMap.ContainsKey($accountId)) { throw "Sideload auth-map CSV '$Path' contains a DUPLICATE UpdateAuthAccountId '$accountId' (row $rowIndex). Each account id must be unique." } $keyVaultName = ([string]$row.KeyVaultName).Trim() $usernameSecret = ([string]$row.UsernameSecretName).Trim() $passwordSecret = ([string]$row.PasswordSecretName).Trim() if ([string]::IsNullOrWhiteSpace($keyVaultName)) { throw "Sideload auth-map CSV '$Path' row $rowIndex (UpdateAuthAccountId '$accountId') has an empty KeyVaultName." } if ([string]::IsNullOrWhiteSpace($usernameSecret)) { throw "Sideload auth-map CSV '$Path' row $rowIndex (UpdateAuthAccountId '$accountId') has an empty UsernameSecretName." } if ([string]::IsNullOrWhiteSpace($passwordSecret)) { throw "Sideload auth-map CSV '$Path' row $rowIndex (UpdateAuthAccountId '$accountId') has an empty PasswordSecretName." } # Optional columns are tolerated as absent properties. $remotingTargetFqdn = if ($columns -contains 'RemotingTargetFqdn') { ([string]$row.RemotingTargetFqdn).Trim() } else { '' } $fqdnSuffix = if ($columns -contains 'FqdnSuffix') { ([string]$row.FqdnSuffix).Trim() } else { '' } $authMechanism = if ($columns -contains 'AuthMechanism') { ([string]$row.AuthMechanism).Trim() } else { '' } $importSharePath = if ($columns -contains 'ImportSharePath') { ([string]$row.ImportSharePath).Trim() } else { '' } $authMap[$accountId] = [PSCustomObject]@{ UpdateAuthAccountId = $accountId KeyVaultName = $keyVaultName UsernameSecretName = $usernameSecret PasswordSecretName = $passwordSecret RemotingTargetFqdn = $remotingTargetFqdn FqdnSuffix = $fqdnSuffix AuthMechanism = $authMechanism ImportSharePath = $importSharePath } } return $authMap } |