samples/_default/.config-utils.ps1
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 = "") { $json = get-content $file | convertfrom-json $components = $path.split(":") $node = $json foreach ($component in $components) { $node = $node.$component } return $node } function set-appsettings( [Parameter(Mandatory = $true)] $file, [Parameter(Mandatory = $true)] $path, [Parameter(Mandatory = $true)] $value ) { $json = get-content $file | convertfrom-json -AsHashtable $components = $path.split(":") $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 } function test-isSpecialValue($v) { return $v -and $v -is [string] -and ($v.StartsWith("user-secrets:") -or $v.StartsWith("keyvault:")); } function resolve-value($v) { $v = $value.$key if (!(test-isSpecialValue $v)) { return $v } if ($v -and $v -is [string] -and $v.StartsWith("user-secrets:")) { $secretName = $v.Substring("user-secrets:".Length) $secrets = dotnet user-secrets -p $dir list --json | ? { !$_.StartsWith("//") } | ConvertFrom-Json -AsHashtable 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?" continue } 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." continue } $v = $secrets[$secretName] } if ($v -and $v -is [string] -and $v.StartsWith("keyvault:")) { $splits = $v.Substring("keyvault:".Length).Split("/") $v = get-keyvaultSecret $splits[0] $splits[1] } return $v } function set-appsettingsobject( [Parameter(Mandatory = $true)]$file, [Parameter(Mandatory = $true)]$value ) { $dir = split-path $file foreach ($key in $value.keys) { if ($key.StartsWith("__")) { continue } $v = resolve-value $value.$key set-appsettings $file -path $key -value $v } } function get-appsettingsObject( [Parameter(Mandatory = $true)][string]$file, [Parameter(Mandatory = $true)]$options ) { foreach ($optionSet in $options.keys) { write-verbose "checking '$optionSet'" $option = $options[$optionSet] $isMatch = $true foreach ($key in $option.keys) { Write-Verbose "getting key $key=$v" $v = get-appsettings $file $key if ($v -is [string] -and $v -ne $option[$key] -and !(test-isSpecialValue $option[$key])) { Write-Verbose "key $key=$v does not match options[$optionSet].$key=$($option[$key])" $isMatch = $false # if any of the keys do not match, break out of the loop break } } if ($isMatch) { return @{ value = $optionSet; Active = $optionSet } } } return @{ value = "?"; } } function test-azureAccount($value) { $account = az account show | ConvertFrom-Json $targetSubscription = $value["__AzureSubscription"]; if (!$targetSubscription) { write-warning "no '__AzureSubscription' subscription specified in $($value | ConvertTo-Json)" return $true } $targetTenant = $value["__AzureTenant"] if ($account) { if (($account.name -eq $targetSubscription -or $account.id -eq $targetSubscription)) { write-host "✅ active azure account: $($account.name) [$($account.id)]" write-host " active tenant: $($account.tenantId)" return $true } else { write-host "❌ active azure account: $($account.name) [$($account.id)] does not match target subscription '$targetSubscription'" } } else { write-host "no active azure account" } return $false } function set-azureAccount($value) { $targetSubscription = $value["__AzureSubscription"]; $targetTenant = $value["__AzureTenant"] if (test-azureAccount $value) { return } Write-Host "Logging into azure..." $a = @() if ($targetTenant) { $a += "--tenant", $targetTenant } az login @a Write-Host "Selecting subscription $targetSubscription..." az account set --subscription $targetSubscription } function get-dockerContainer( [Parameter(Mandatory = $false)] $displayName, [Parameter(ParameterSetName = "port", Mandatory = $true)] $port, [Parameter(ParameterSetName = "name", Mandatory = $true)] $name ) { $dockerVersionStr = docker -v $m = $dockerVersionStr -match "[0-9]+\.[0-9]+\.[0-9]+" $dockerVersion = [version]::Parse($matches[0]) if ($dockerVersion.Major -lt 24) { return @{ Value = "? (Docker version >=24 required)" } } $containers = docker ps -a --format json | convertfrom-json if ($port -ne $null) { $container = $containers | ? { $_.Ports.contains([string]$port) } } elseif ($name -ne $null) { if ($name.StartsWith("/")) { $regexp = [regex]::new($name.Trim("/")) $container = $containers | ? { $regexp.IsMatch($_.Image) -or $regexp.IsMatch($_.Names) } } else { $container = $containers | ? { $_.Image.StartsWith($name) -or $_.Names.StartsWith($name) } } } write-verbose "== all containers: ==" docker -v | write-verbose docker compose version | write-verbose $containers | convertto-json | write-verbose write-verbose "== containers END ==" return $container } function test-dockerContainer( [Parameter(Mandatory = $false)] $displayName, [Parameter(ParameterSetName = "port", Mandatory = $true)] $port, [Parameter(ParameterSetName = "name", Mandatory = $true)] $name) { $container = $null if ($port) { $container = get-dockerContainer -displayName $displayName -port $port if (!$container) { write-host "❌ $displayName container at port $port not found" return $false } } if ($name) { $container = get-dockerContainer -displayName $displayName -name $name if (!$container) { write-host "❌ $displayName container matching name '$name' not found" return $false } } $isvalid = $container -and $container.state -eq "running" if ($isvalid ) { write-host "✅ $displayName running" } else { write-host "❌ $displayName not running" } return $isvalid } function test-dockerDb($containerName, $dbName = "rls_lagerman") { write-verbose "checking if db is running..." $container = get-dockerContainer -name $containerName if (!$container) { throw "no containers found with name matching '$containerName'" } if (@($container).Length -gt 1) { throw "multiple containers found with name matching '$containerName'" } $containerName = $container.Names write-verbose "checking sql connection to $containerName..." $retries = 5 $backoff = 5 for ($i = $retries; $i -ge 0; $i++) { $result = docker exec $containerName "/opt/mssql-tools/bin/sqlcmd" -S localhost -U sa -P '12345678!aA#' -Q "print 'select from inisettings'; use [$dbName]; select top 10 * from inisitesettings;" $result if ($lastExitCode -eq 0 -and $result[0].StartsWith("select from inisettings")) { write-verbose "DB is running" break } else { write-verbose "DB is not running" if ($i -eq 0) { return $false } write-verbose "retrying in $backoff s..." Start-Sleep -Seconds $backoff } } write-verbose "DONE exitCode=$LastExitCode" return $true } function get-envsetting($file, $path) { if (!(test-path $file)) { return $null } $env = get-content $file | ConvertFrom-StringData return $env.$path } function set-envsetting($file, $path, $value) { $envData = @{} if (test-path $file) { $envData = get-content $file | ConvertFrom-StringData } $hash = [ordered]@{} $envData.keys | % { $hash[$_] = $envData.$_ } $hash[$path] = $value $hash.GetEnumerator() | % { "$($_.key)=$($_.value)" } | Out-File $file } function set-envsettingsObject($file, $path, $value) { $dir = split-path $file foreach ($key in $value.keys) { if ($key.StartsWith("__")) { continue } $v = resolve-value $value.$key set-envsetting $file -path $key -value $v } } function get-envsettingsObject( [Parameter(Mandatory = $true)][string]$file, [Parameter(Mandatory = $true)]$options ) { if (!(test-path $file)) { return @{ value = $null; Active = $null } } $value = get-content $file | ConvertFrom-StringData foreach ($optionSet in $options.keys) { $option = $options.$optionSet $isMatch = $true foreach ($key in $option.keys) { $v = $value.key Write-Verbose "getting key $key=$v" if ($v -is [string] -and $v -ne $option.$key -and !(test-isSpecialValue $option.$key)) { Write-Verbose "key $key=$v does not match $option.$key" $isMatch = $false break } } if ($isMatch) { return @{ value = $value; Active = $optionSet } } } return @{ value = $value; Active = $null } } function get-keyvaultSecret($keyvault, $secret) { $secret = az keyvault secret show --vault-name $keyvault --name $secret | ConvertFrom-Json return $secret.value } Function Create-JWT( [Parameter(Mandatory = $true)]$headers, [Parameter(Mandatory = $true)]$payload, [Parameter(Mandatory = $true)]$secret ) { $headersJson = $headers | ConvertTo-Json -Compress $payloadJson = $payload | ConvertTo-Json -Compress $headersEncoded = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($headersJson), [Base64FormattingOptions]::None).TrimEnd('=') $payloadEncoded = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($payloadJson), [Base64FormattingOptions]::None).TrimEnd('=') $content = "$($headersEncoded).$($payloadEncoded)" $hmacsha = New-Object System.Security.Cryptography.HMACSHA256 $hmacsha.key = [Text.Encoding]::UTF8.GetBytes($secret) $bytesToSign = [Text.Encoding]::UTF8.GetBytes($content) $signatureByte = $hmacsha.ComputeHash($bytesToSign) $signature = [System.Convert]::ToBase64String($signatureByte, [Base64FormattingOptions]::None).Replace('+', '-').Replace('/', '_').Replace("=", "").TrimEnd('=') $jwt = "$($headersEncoded).$($payloadEncoded).$($signature)" return $jwt } Function Get-RezlynxToken { param( [Parameter(Mandatory = $true)]$signingKey, [Parameter(Mandatory = $true)]$siteId, $groupId = "", $username = "SUPERVISOR", $audience = "GhpServices", $clientId = "RezlynxUi", $expireAfterMin = 60 ) # rlx uses symmetric key for signature $header = @{ "alg" = "HS256" "typ" = "JWT" } # this is the format of Rlx auth token $payload = @{ "aud" = "$audience" "username" = "$username" "gl_username" = "$username" "gl_group" = "$groupId" "group" = "$groupId" "siteId" = "$siteId" "gl_site" = "$siteId" "client_id" = "$clientId" "nbf" = 1677747257 "exp" = 1677748457 "iat" = 1677747257 "iss" = "rezlynx" } $epochNow = [int]([DateTime]::UtcNow - [DateTime]('1970,1,1')).TotalSeconds $epochExpiry = [int]([DateTime]::UtcNow.AddMinutes($expireAfterMin) - [DateTime]('1970,1,1')).TotalSeconds $payload.nbf = $epochNow $payload.iat = $epochNow $payload.exp = $epochExpiry $jwt = Create-JWT -headers $header -payload $payload -secret $signingKey return $jwt } function get-xmlconfig($file, $path) { if (!(test-path $file)) { return $null } $config = [xml](get-content $file) $splits = $path.Split("/") $current = $config $currentPath = "" foreach ($split in $splits) { if ($current[$split] -eq $null) { throw "Could not find $split in $currentPath" } $current = $current[$split] $currentPath += "/$split" } return $current.InnerXml } function set-xmlconfig($file, $path, $value) { $fullPath = [System.IO.Path]::GetFullPath($file) if ((test-path $fullPath)) { $config = [xml](get-content $fullPath) } else { $config = [xml]"" } $splits = $path.Split("/") $current = $config $currentPath = "" foreach ($split in $splits) { if ($current[$split] -eq $null) { $current.AppendChild($config.CreateElement($split)) } $current = $current[$split] $currentPath += "/$split" } $current.InnerXml = $value $config.Save($fullPath) } function test-httpurl($url) { try { $result = Invoke-WebRequest -Uri $url -Method Get -UseBasicParsing -ErrorAction SilentlyContinue return ($result.statuscode -eq 200) } catch { if ($_.Exception.StatusCode -eq 404) { # 404 means the service is there and responding, so treat it as success return $true } return $false } } |