.config-utils.ps1
|
#Requires -Version 7.0 function replace-values($object, $values) { $result = [ordered]@{} foreach ($key in $object.keys) { $value = $object.$key if ($value -is [string] -and $values.$value) { $result.$key = $values.$value } elseif ($value -is [hashtable]) { $result.$key = replace-values $value $values } else { $result.$key = $value } } return $result } function inherit-base($base, $module) { write-verbose "inheriting base module $($base.name) for $($module.name)" foreach ($key in $base.args.keys) { if ($module.inherit.args[$key] -eq $null) { $module.inherit.args[$key] = $base.args[$key] } } $module.get = { param($path, $options) Invoke-Command -ScriptBlock $base.get -ArgumentList @($module, $path, $options) }.GetNewClosure() $module.set = { param($path, $value, $key, $additionalOpts) . $PSScriptRoot/.config-utils.ps1 $value = replace-values $module.inherit.template $value Invoke-Command -ScriptBlock $base.set -ArgumentList @($module, $path, $value, $key, $additionalOpts) }.GetNewClosure() $module.options = $base.options $module.validate = $base.validate return $module } function make-alias($moduleName, $path = $null, $workingDir = $null, $hooks) { if ($workingDir -eq $null) { $workingDir = $PSScriptRoot } $modulePath = $moduleName if ($path -ne $null) { $modulePath = "$moduleName/$path" } return @{ get = { param($path, $options) pushd $workingDir try { $r = & .\configure.ps1 -command get -module $modulePath -porcelain if ($hooks -and $hooks.get) { $subResult = & $hooks.get -path $path -options $options if ($subResult) { foreach ($key in $subResult.keys) { $r.$key = $subResult[$key] } } } return @{ Value = $r.Value; Active = $r.Active; IsValid = $r.IsValid } } finally { popd } }.GetNewClosure() set = { param($path, $v, $optName) $r = $null pushd $workingDir try { . .\.configuration.map.ps1 $module = $modules.$moduleName $moduleCommand = $module.set $r = Invoke-Command -ScriptBlock $moduleCommand -ArgumentList @($path, $v, $optName) } finally { popd } if ($hooks -and $hooks.set) { $subResult = & $hooks.set -path $path -value $v -key $optName } return $r }.GetNewClosure() validate = { param($path, $v, $optName) pushd $workingDir try { $r = & .\configure.ps1 -command validate -module $modulePath -porcelain return $r.IsValid } finally { popd } }.GetNewClosure() options = { param($path) pushd $workingDir try { $r = & .\configure.ps1 -command options -module $modulePath -porcelain if ($hooks -and $hooks.options) { $subResult = & $hooks.options -path $path -options $r if ($subResult) { return $subResult } } return $r } finally { popd } }.GetNewClosure() } } function get-appsettings([Parameter(Mandatory = $true)]$file, $path = "") { try { if (!(Test-Path $file)) { throw "File not found: $file" } $json = get-content $file | convertfrom-json $components = $path.split(":") $node = $json foreach ($component in $components) { if (!$component) { continue } $node = $node.$component } return $node } catch { Write-Error "Failed to get app settings from $file at path '$path': $($_.Exception.Message)" throw } } function set-appsettings( [Parameter(Mandatory = $true)] $file, [Parameter(Mandatory = $true)] $path, [Parameter(Mandatory = $true)] $value ) { try { if (!(Test-Path $file)) { throw "File not found: $file" } $json = get-content $file | convertfrom-json -AsHashtable $components = $path.split(":", [System.StringSplitOptions]::RemoveEmptyEntries) $node = $json for ($i = 0; $i -lt $components.Count - 1; $i++) { $component = $components[$i] if ($node.$component -eq $null) { $node.$component = @{} } $node = $node.$component } $leaf = $components[$components.Count - 1] $node.$($leaf) = $value $json | convertto-json -Depth 100 | set-content $file } catch { Write-Error "Failed to set app settings in $file at path '$path': $($_.Exception.Message)" throw } } function test-isSpecialValue($v) { if (!$v) { return $false } if ($v -is [string]) { return $v.StartsWith("user-secrets:") -or $v.StartsWith("keyvault:") } return $false } function resolve-value($v, [switch]$recurse, [string]$dir) { if (!$v) { return $v } if ($v -is [string]) { if (!(test-isSpecialValue $v)) { return $v } if ($v.StartsWith("user-secrets:")) { $secretName = $v.Substring("user-secrets:".Length) $secrets = get-user-secrets $dir if ($LASTEXITCODE -ne 0) { write-warning "cannot set '$secretName': failed to list user secrets for project in '$dir'. Do you have 'dotnet user-secrets' installed?" return $v } if (!$secrets.ContainsKey($secretName)) { write-warning "secret '$secretName' not found in user secrets. Please run 'dotnet user-secrets -p $dir set $secretName <value>' to set the secret." return $v } $v = $secrets[$secretName] return $v } if ($v.StartsWith("keyvault:")) { $splits = $v.Substring("keyvault:".Length).Split("/") $v = get-keyvaultSecret $splits[0] $splits[1] return $v } } elseif ($v -is [hashtable]) { if ($recurse) { $result = [ordered]@{} foreach ($key in $v.keys) { $result[$key] = resolve-value $v[$key] -recurse:$recurse -dir $dir } return $result } return $v } return $v } function get-user-secrets($dir) { $secrets = @{} try { $output = dotnet user-secrets list --project $dir 2>&1 if ($LASTEXITCODE -ne 0) { return $secrets } foreach ($line in $output) { if ($line -match "^(.+?)\s*=\s*(.*)$") { $secrets[$matches[1]] = $matches[2] } } } catch { Write-Warning "Failed to retrieve user secrets: $($_.Exception.Message)" } return $secrets } function get-keyvaultSecret($vaultName, $secretName) { try { $secret = az keyvault secret show --vault-name $vaultName --name $secretName --query value -o tsv 2>$null if ($LASTEXITCODE -eq 0) { return $secret } else { Write-Warning "Failed to retrieve secret '$secretName' from vault '$vaultName'" return $null } } catch { Write-Warning "Error accessing Key Vault: $($_.Exception.Message)" return $null } } function Get-SSOToken($env) { if (!(which gstln-login)) { npm install -g git+https://dev.azure.com/guestlinelabs/Mandalore/_git/gstln-login } $token = gstln-login -env $env | ConvertFrom-Json return $token.accessToken } function get-envsetting($key, $envFile = ".env") { if (!(Test-Path $envFile)) { return $null } $content = Get-Content $envFile foreach ($line in $content) { if ($line -match "^$key\s*=\s*(.*)$") { return $matches[1].Trim('"') } } return $null } function set-envsetting($key, $value, $envFile = ".env") { $lines = @() $found = $false if (Test-Path $envFile) { $lines = Get-Content $envFile } for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i] -match "^$key\s*=") { $lines[$i] = "$key=$value" $found = $true break } } if (!$found) { $lines += "$key=$value" } $lines | Set-Content $envFile } function get-envsettingsObject($envFile = ".env") { $settings = @{} if (!(Test-Path $envFile)) { return $settings } $content = Get-Content $envFile foreach ($line in $content) { if ($line -match "^([^=]+)\s*=\s*(.*)$") { $key = $matches[1].Trim() $value = $matches[2].Trim('"') $settings[$key] = $value } } return $settings } function set-envsettingsObject($settings, $envFile = ".env") { $lines = @() foreach ($key in $settings.Keys) { $lines += "$key=$($settings[$key])" } $lines | Set-Content $envFile } function execute-sql($connectionString, $query) { try { $connection = New-Object System.Data.SqlClient.SqlConnection($connectionString) $connection.Open() $command = New-Object System.Data.SqlClient.SqlCommand($query, $connection) $result = $command.ExecuteScalar() $connection.Close() return $result } catch { Write-Error "SQL execution failed: $($_.Exception.Message)" throw } } function Create-JWT($header, $payload, $secret) { $headerJson = $header | ConvertTo-Json -Compress $payloadJson = $payload | ConvertTo-Json -Compress $headerEncoded = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($headerJson)).TrimEnd('=').Replace('+', '-').Replace('/', '_') $payloadEncoded = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($payloadJson)).TrimEnd('=').Replace('+', '-').Replace('/', '_') $toSign = "$headerEncoded.$payloadEncoded" $secretBytes = [Text.Encoding]::UTF8.GetBytes($secret) $toSignBytes = [Text.Encoding]::UTF8.GetBytes($toSign) $hmac = New-Object System.Security.Cryptography.HMACSHA256($secretBytes) $signature = $hmac.ComputeHash($toSignBytes) $signatureEncoded = [Convert]::ToBase64String($signature).TrimEnd('=').Replace('+', '-').Replace('/', '_') return "$toSign.$signatureEncoded" } function test-httpUrl($url) { try { $response = Invoke-WebRequest -Uri $url -Method Head -TimeoutSec 10 -UseBasicParsing return $response.StatusCode -eq 200 } catch { return $false } } function get-xmlconfiguration($file, $path) { if (!(Test-Path $file)) { throw "XML file not found: $file" } [xml]$xml = Get-Content $file if ([System.IO.Path]::IsPathRooted($path)) { $xpath = $path } else { $xpath = "//$path" } $node = $xml.SelectSingleNode($xpath) if ($node) { if ($node.InnerText) { return $node.InnerText } else { return $node.OuterXml } } return $null } function set-xmlconfiguration($file, $path, $value) { if (!(Test-Path $file)) { throw "XML file not found: $file" } [xml]$xml = Get-Content $file if ([System.IO.Path]::IsPathRooted($path)) { $xpath = $path } else { $xpath = "//$path" } $node = $xml.SelectSingleNode($xpath) if ($node) { $node.InnerText = $value $xml.Save($file) } else { throw "XPath not found: $path" } } function compare-optionsWithValues( [Parameter(Mandatory = $true)]$options, [Parameter(Mandatory = $true)]$getValueFunction, [string] $path = "" ) { foreach ($optionSet in $options.keys) { $option = $options.$optionSet if ($path) { $option = $option.$path } $matchDict = @{} foreach ($key in $option.keys) { $actualValue = & $getValueFunction $key $optionValue = $option.$key $result = $null if ($actualValue -isnot [string]) { $result = "skipped:not a string" } elseif (test-isSpecialValue $optionValue) { $result = "skipped:special value" } if ($result -ne $null) { $matchDict[$key] = @{ OptionKey = $optionSet Result = $result OptionValue = $optionValue ActualValue = $actualValue } continue } if ($actualValue -eq $optionValue) { $result = "match" } else { $result = "not-match" } $matchDict[$key] = @{ OptionKey = $optionSet Result = $result OptionValue = $optionValue ActualValue = $actualValue } } $notMatch = $matchDict.values | ? { $_.Result -eq "not-match" } $skipped = $matchDict.values | ? { $_.Result -eq "skipped" } $match = $matchDict.values | ? { $_.Result -eq "match" } if ($notMatch.Count -eq 0 -and $match.Count -gt 0) { return @{ Active = $optionSet } } else { $matchDict | ConvertTo-Json | write-verbose } } return @{ Active = $null } } function get-dockerContainerVersion($containerName) { try { $output = docker ps --filter "name=$containerName" --format "{{.Image}}" 2>$null if ($LASTEXITCODE -eq 0 -and $output) { if ($output -match ":(.+)$") { return $matches[1] } return "latest" } return $null } catch { return $null } } function start-dockerContainer($containerName, $imageName, $ports = @(), $environment = @()) { try { $existing = docker ps -a --filter "name=$containerName" --format "{{.Names}}" 2>$null if ($existing -eq $containerName) { $status = docker ps --filter "name=$containerName" --format "{{.Status}}" 2>$null if (!$status) { Write-Host "Starting existing container: $containerName" docker start $containerName | Out-Null } return } $runArgs = @("run", "-d", "--name", $containerName) foreach ($port in $ports) { $runArgs += "-p" $runArgs += $port } foreach ($env in $environment) { $runArgs += "-e" $runArgs += $env } $runArgs += $imageName Write-Host "Creating and starting container: $containerName" & docker @runArgs | Out-Null } catch { Write-Error "Failed to start Docker container '$containerName': $($_.Exception.Message)" throw } } |