Modules/AzureDevOpsDsc.Common/Api/Functions/Private/Helper/ConvertTo-AzDoVariableGroupVariables.ps1
|
<#
.SYNOPSIS Normalizes a variable group's variables to the shape the Azure DevOps API stores. .DESCRIPTION A configuration may write a variable either as a plain value or as a hashtable that also says whether it is secret: @{ APP_ENV = 'production' } @{ APP_KEY = @{ value = 'p@ssw0rd'; isSecret = $true } } The API stores every variable as @{ value = ...; isSecret = <bool> }, and returns a secret one with 'value' = $null and 'isSecret' = $true. This function turns either input shape - and the API's own output, as a hashtable or as the PSCustomObject a REST call or a cache import returns - into that one shape, so New-/Set-DevOpsVariableGroup send it and Get-AzDoVariableGroup compares it without each caring which form the caller used. Running it over its own output is a no-op. Key names inside an entry are matched without regard to case. 'value' and 'isSecret' are understood; 'isReadOnly' is carried through unchanged (the API returns it, and a Set that re-sends the live variables must not drop it); any other key is an error, so a mistyped secret marker ('secret', 'IsSecrets') fails loudly rather than silently storing the value in the clear. A secret entry with no 'value' is normalized to value = $null. Azure DevOps keeps the stored secret when a PUT carries a null value for a secret variable, so that is the way to manage a secret variable's existence without re-supplying its value. .PARAMETER Variables The variables as a hashtable (either shape above) or as the property bag the API returns. $null is treated as no variables. .OUTPUTS System.Collections.Hashtable @{ <name> = @{ value = <string or $null>; isSecret = <bool> } } for every variable. .EXAMPLE ConvertTo-AzDoVariableGroupVariables -Variables @{ A = '1'; B = @{ value = 'x'; IsSecret = $true } } Returns @{ A = @{ value = '1'; isSecret = $false }; B = @{ value = 'x'; isSecret = $true } }. #> function ConvertTo-AzDoVariableGroupVariables { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter()] [AllowNull()] [System.Object]$Variables ) # Reads a hashtable or an object's properties as (Name, Value) pairs. A string or a value type # is not a bag of variables and is reported as such by the caller. function Get-BagEntries { param ([System.Object]$Bag) $entries = [System.Collections.Generic.List[System.Object]]::new() if ($Bag -is [System.Collections.IDictionary]) { foreach ($key in $Bag.Keys) { $entries.Add([PSCustomObject]@{ Name = [System.String]$key; Value = $Bag[$key] }) } } else { foreach ($property in $Bag.PSObject.Properties) { $entries.Add([PSCustomObject]@{ Name = $property.Name; Value = $property.Value }) } } return ,$entries } function Test-IsBag { param ([System.Object]$Candidate) if ($null -eq $Candidate) { return $false } if ($Candidate -is [System.Collections.IDictionary]) { return $true } if ($Candidate -is [System.String]) { return $false } if ($Candidate -is [System.ValueType]) { return $false } if ($Candidate -is [System.Collections.IEnumerable]) { return $false } return $true } $normalized = @{} if ($null -eq $Variables) { return $normalized } if (-not (Test-IsBag -Candidate $Variables)) { throw "[ConvertTo-AzDoVariableGroupVariables] Variables must be a hashtable of variable name to value, not a '$($Variables.GetType().Name)'." } foreach ($entry in (Get-BagEntries -Bag $Variables)) { $name = $entry.Name $raw = $entry.Value if ([System.String]::IsNullOrWhiteSpace($name)) { throw "[ConvertTo-AzDoVariableGroupVariables] A variable name must not be empty." } if (-not (Test-IsBag -Candidate $raw)) { # Plain value: a non-secret variable. $null becomes an empty string, as the UI stores it. $normalized[$name] = @{ value = if ($null -eq $raw) { '' } else { [System.String]$raw } isSecret = $false } continue } $value = $null $isSecret = $false $isReadOnly = $null foreach ($field in (Get-BagEntries -Bag $raw)) { switch ($field.Name.ToLowerInvariant()) { 'value' { $value = if ($null -eq $field.Value) { $null } else { [System.String]$field.Value } } 'issecret' { $isSecret = if ($field.Value -is [System.String]) { [System.Boolean]::Parse($field.Value) } else { [System.Boolean]$field.Value } } 'isreadonly' { $isReadOnly = if ($field.Value -is [System.String]) { [System.Boolean]::Parse($field.Value) } else { [System.Boolean]$field.Value } } default { throw "[ConvertTo-AzDoVariableGroupVariables] Variable '$name' has an unrecognised key '$($field.Name)'. A variable is a plain value or a hashtable with 'value' and 'isSecret'." } } } # A non-secret variable always has a string value; a secret one may have none (keep the # stored value). @{ isSecret = $false } with no value is the same as a plain empty value. if (-not $isSecret -and $null -eq $value) { $value = '' } $normalizedEntry = @{ value = $value isSecret = $isSecret } if ($null -ne $isReadOnly) { $normalizedEntry.isReadOnly = $isReadOnly } $normalized[$name] = $normalizedEntry } return $normalized } |