Bin/Import-OpenStackRCFile.ps1
|
function Import-OpenStackRCFile { <# .SYNOPSIS Imports an OpenStack RC ("openrc") shell script as PowerShell environment variables. .DESCRIPTION Parses every `export KEY=VALUE` line in an OpenStack RC file (e.g. as downloaded from Horizon's "Download OpenStack RC File") and sets each as a PowerShell environment variable ($env:KEY). Works generically - it does not hard-code specific OS_* variable names, so it picks up whatever the RC file actually exports. Comments, blank lines, and `unset`/`if [ -z ... ]` guard lines are ignored, since those have no literal value to import. Some RC files don't embed a literal OS_PASSWORD value and instead read it interactively in bash, e.g.: read -sr OS_PASSWORD_INPUT export OS_PASSWORD=$OS_PASSWORD_INPUT Since this function only parses the file (it doesn't execute it as bash), a value like that is an unresolved shell variable reference, not a real password. Import-OpenStackRCFile detects this pattern and treats it the same as OS_PASSWORD being missing entirely: it prompts interactively with a masked SecureString read instead of importing the literal text "$OS_PASSWORD_INPUT". Uses the Env: PSDrive rather than any Windows-specific API, so it works the same way on Windows and Linux PowerShell 7+. .PARAMETER Path Path to the OpenStack RC shell script (.sh) to import. .EXAMPLE Import-OpenStackRCFile -Path .\Fanap-kish.sh Imports every exported variable from the file. Since this file has a literal OS_PASSWORD value, no prompt is shown. .EXAMPLE Import-OpenStackRCFile -Path .\Fanap-Sharepoint-openrc.sh Imports every exported variable. Since this file's OS_PASSWORD line is an unresolved shell variable reference (from an interactive `read` in bash), the user is prompted for the password instead. .EXAMPLE $result = Import-OpenStackRCFile -Path .\Fanap-kish.sh Write-Host "Connected to $($result.Endpoint) as $($result.Username)" Captures the returned summary object for use in a larger script. .OUTPUTS PSCustomObject with Endpoint, ProjectName, Username, VariablesImported, and SourceFile. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory = $true, Position = 0)] [ValidateNotNullOrEmpty()] [string]$Path ) if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { throw "OpenStack RC file not found: $Path" } $resolvedPath = (Resolve-Path -LiteralPath $Path).Path $lines = Get-Content -LiteralPath $resolvedPath # Matches: export KEY=VALUE (VALUE optionally single/double-quoted, or empty) $exportPattern = '^\s*export\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$' # Detects a value that is itself an unresolved shell variable reference, e.g. $FOO or ${FOO} $shellVarRefPattern = '^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$' $imported = [ordered]@{} $skipped = [System.Collections.Generic.List[string]]::new() foreach ($line in $lines) { $trimmed = $line.Trim() if (-not $trimmed -or $trimmed.StartsWith('#')) { continue } $match = [regex]::Match($trimmed, $exportPattern) if (-not $match.Success) { continue } $key = $match.Groups[1].Value $value = $match.Groups[2].Value.Trim() # Strip a single layer of matching quotes: "value" or 'value' -> value if ($value.Length -ge 2 -and ( ($value.StartsWith('"') -and $value.EndsWith('"')) -or ($value.StartsWith("'") -and $value.EndsWith("'")) )) { $value = $value.Substring(1, $value.Length - 2) } if ([string]::IsNullOrEmpty($value)) { $skipped.Add($key) continue } # A value like $OS_PASSWORD_INPUT can't be resolved from a static parse of the file - # it only has a real value once bash actually executes the preceding `read` command. if ($value -match $shellVarRefPattern) { $skipped.Add($key) continue } Set-Item -Path "Env:$key" -Value $value $imported[$key] = $value } # OS_PASSWORD fallback: prompt if it was missing, empty, or an unresolved shell reference. if (-not $imported.Contains('OS_PASSWORD')) { $promptUser = if ($imported.Contains('OS_USERNAME')) { $imported['OS_USERNAME'] } else { $env:OS_USERNAME } $promptProject = if ($imported.Contains('OS_PROJECT_NAME')) { $imported['OS_PROJECT_NAME'] } else { $env:OS_PROJECT_NAME } $securePassword = Read-Host -Prompt "Enter OpenStack password for user '$promptUser' (project '$promptProject')" -AsSecureString $plainPassword = [System.Net.NetworkCredential]::new('', $securePassword).Password Set-Item -Path 'Env:OS_PASSWORD' -Value $plainPassword # Store a masked placeholder in the summary hashtable - never the real secret - # purely so the variable count and success report are accurate. $imported['OS_PASSWORD'] = '********' } if (-not $imported.Contains('OS_AUTH_URL') -or [string]::IsNullOrEmpty($imported['OS_AUTH_URL'])) { Write-Warning 'OS_AUTH_URL was not found in the RC file - OpenStack Endpoint cannot be shown.' $endpoint = $null } else { # Strip a trailing :port from the endpoint, e.g. http://host:5000 -> http://host $endpoint = [regex]::Replace($imported['OS_AUTH_URL'], ':\d+$', '') } Write-Host "OpenStack RC file imported successfully from '$resolvedPath' ($($imported.Count) variable(s) set)." -ForegroundColor Green if ($endpoint) { Write-Host "OpenStack Endpoint : $endpoint" -ForegroundColor Cyan } if ($skipped.Count -gt 0) { Write-Verbose "Skipped empty/unresolved value(s) for: $($skipped -join ', ')" } [PSCustomObject]@{ Endpoint = $endpoint ProjectName = $imported['OS_PROJECT_NAME'] Username = $imported['OS_USERNAME'] VariablesImported = $imported.Count SourceFile = $resolvedPath } } |