- Drop existing indexes on DeploymentJobTargets and DeploymentJobSteps - Alter Status column to nvarchar(450) in DeploymentJobTargets, DeploymentJobSteps, and DeploymentJobs - Add new columns: Finished, OutputMetadataJson, Started to DeploymentJobTargets; ErrorMessage, Finished, OutputMetadataJson, Started to DeploymentJobSteps; CorrelationId, HeartbeatAt, Priority, RowVersion, ScheduledAt, WorkerName to DeploymentJobs - Create new indexes for improved query performance - Add constraints to ensure OutputMetadataJson is valid JSON - Record migration in __EFMigrationsHistory
422 lines
15 KiB
PowerShell
422 lines
15 KiB
PowerShell
param(
|
|
[string]$ApiBaseUrl = 'http://localhost:5286/api',
|
|
|
|
[string]$BgwRoot = ('F:\Kunden Auftr' + [char]0x00E4 + 'ge\BGW'),
|
|
|
|
[string]$DeploymentBatchId = '80000000-0000-0000-0000-000000000101',
|
|
|
|
[string]$MergeModulePath = 'F:\Projekte\Coding\PowerShell\Merge-DSCConfigurationData\Merge-DSCConfigurationData.psd1',
|
|
|
|
[string]$ResolveModulePath = 'F:\Projekte\Coding\PowerShell\Resolve-DSCConfigurationData\Resolve-DSCConfigurationData.psd1',
|
|
|
|
[int]$ApiTimeoutSec = 30,
|
|
|
|
[bool]$UseDefaultCredentials = $true
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
function Invoke-TestBgwMergeApiJson {
|
|
param(
|
|
[ValidateSet('GET', 'POST', 'PUT', 'DELETE')]
|
|
[string]$Method,
|
|
|
|
[string]$Path
|
|
)
|
|
|
|
$uri = ('{0}/{1}' -f $ApiBaseUrl.TrimEnd('/'), $Path.TrimStart('/'))
|
|
$parameters = @{
|
|
Method = $Method
|
|
Uri = $uri
|
|
TimeoutSec = $ApiTimeoutSec
|
|
ErrorAction = 'Stop'
|
|
}
|
|
|
|
if ($UseDefaultCredentials) {
|
|
$parameters.UseDefaultCredentials = $true
|
|
}
|
|
|
|
try {
|
|
Invoke-RestMethod @parameters
|
|
}
|
|
catch {
|
|
$responseBody = '<empty response body>'
|
|
if ($_.Exception.Response) {
|
|
try {
|
|
$stream = $_.Exception.Response.GetResponseStream()
|
|
if ($stream) {
|
|
$reader = [System.IO.StreamReader]::new($stream)
|
|
$text = $reader.ReadToEnd()
|
|
if (-not [string]::IsNullOrWhiteSpace($text)) {
|
|
$responseBody = $text
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
$responseBody = '<could not read response body>'
|
|
}
|
|
}
|
|
|
|
throw "API request failed. Method=[$Method], Uri=[$uri], Response=[$responseBody]. $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
function ConvertTo-TestBgwMergeConfigurationValue {
|
|
param([object]$Value)
|
|
|
|
if ($null -eq $Value) {
|
|
return $null
|
|
}
|
|
|
|
if ($Value -is [string] -or $Value -is [bool] -or $Value -is [int] -or $Value -is [long] -or $Value -is [double] -or $Value -is [decimal]) {
|
|
return $Value
|
|
}
|
|
|
|
if ($Value -is [System.Collections.IDictionary]) {
|
|
$result = [ordered]@{}
|
|
foreach ($key in $Value.Keys) {
|
|
$result[[string]$key] = ConvertTo-TestBgwMergeConfigurationValue -Value $Value[$key]
|
|
}
|
|
|
|
Write-Output -NoEnumerate $result
|
|
return
|
|
}
|
|
|
|
if ($Value -is [pscustomobject]) {
|
|
$result = [ordered]@{}
|
|
foreach ($property in $Value.PSObject.Properties) {
|
|
$result[$property.Name] = ConvertTo-TestBgwMergeConfigurationValue -Value $property.Value
|
|
}
|
|
|
|
Write-Output -NoEnumerate $result
|
|
return
|
|
}
|
|
|
|
if ($Value -is [System.Collections.IEnumerable] -and $Value -isnot [string]) {
|
|
$items = New-Object System.Collections.Generic.List[object]
|
|
foreach ($item in $Value) {
|
|
$items.Add((ConvertTo-TestBgwMergeConfigurationValue -Value $item))
|
|
}
|
|
|
|
Write-Output -NoEnumerate $items.ToArray()
|
|
return
|
|
}
|
|
|
|
return $Value
|
|
}
|
|
|
|
function ConvertFrom-TestBgwMergeTemplateJson {
|
|
param([string]$JsonData)
|
|
|
|
$document = $JsonData | ConvertFrom-Json
|
|
$metadata = ConvertTo-TestBgwMergeConfigurationValue -Value $document.metadata
|
|
if ($null -eq $metadata) {
|
|
$metadata = [ordered]@{}
|
|
}
|
|
|
|
if (-not $metadata.Contains('TemplateType')) {
|
|
$metadata['TemplateType'] = $document.templateType
|
|
}
|
|
|
|
[ordered]@{
|
|
Metadata = $metadata
|
|
Parameters = ConvertTo-TestBgwMergeConfigurationValue -Value $document.parameters
|
|
Variables = ConvertTo-TestBgwMergeConfigurationValue -Value $document.variables
|
|
Resources = ConvertTo-TestBgwMergeConfigurationValue -Value $document.resources
|
|
}
|
|
}
|
|
|
|
function ConvertFrom-TestBgwMergeTargetAssignments {
|
|
param([object[]]$TargetAssignments)
|
|
|
|
@($TargetAssignments | Sort-Object sortOrder | ForEach-Object {
|
|
$nodeData = ConvertTo-TestBgwMergeConfigurationValue -Value ($_.nodeDataJson | ConvertFrom-Json)
|
|
$node = @{}
|
|
|
|
foreach ($key in $nodeData.Keys) {
|
|
if ($key -eq 'nodeName') {
|
|
$node.NodeName = $nodeData[$key]
|
|
}
|
|
else {
|
|
$node[$key] = $nodeData[$key]
|
|
}
|
|
}
|
|
|
|
$node
|
|
})
|
|
}
|
|
|
|
function ConvertTo-TestBgwMergeStableValue {
|
|
param([object]$Value)
|
|
|
|
if ($null -eq $Value) {
|
|
return $null
|
|
}
|
|
|
|
if ($Value -is [string] -or $Value -is [bool] -or $Value -is [int] -or $Value -is [long] -or $Value -is [double] -or $Value -is [decimal]) {
|
|
return $Value
|
|
}
|
|
|
|
if ($Value -is [System.Collections.IDictionary]) {
|
|
$result = [ordered]@{}
|
|
foreach ($key in @($Value.Keys | Sort-Object)) {
|
|
$result[[string]$key] = ConvertTo-TestBgwMergeStableValue -Value $Value[$key]
|
|
}
|
|
|
|
Write-Output -NoEnumerate $result
|
|
return
|
|
}
|
|
|
|
if ($Value -is [pscustomobject]) {
|
|
$result = [ordered]@{}
|
|
foreach ($property in @($Value.PSObject.Properties | Sort-Object Name)) {
|
|
$result[$property.Name] = ConvertTo-TestBgwMergeStableValue -Value $property.Value
|
|
}
|
|
|
|
Write-Output -NoEnumerate $result
|
|
return
|
|
}
|
|
|
|
if ($Value -is [System.Collections.IEnumerable] -and $Value -isnot [string]) {
|
|
$items = New-Object System.Collections.Generic.List[object]
|
|
foreach ($item in $Value) {
|
|
$items.Add((ConvertTo-TestBgwMergeStableValue -Value $item))
|
|
}
|
|
|
|
Write-Output -NoEnumerate $items.ToArray()
|
|
return
|
|
}
|
|
|
|
return $Value
|
|
}
|
|
|
|
function ConvertTo-TestBgwMergeStableJson {
|
|
param([object]$Value)
|
|
|
|
(ConvertTo-TestBgwMergeStableValue -Value $Value) | ConvertTo-Json -Depth 100 -Compress
|
|
}
|
|
|
|
function Invoke-TestBgwMergeSnapshotScript {
|
|
param([string]$Script)
|
|
|
|
$encodedCommand = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($Script))
|
|
$output = & powershell.exe -NoProfile -ExecutionPolicy Bypass -EncodedCommand $encodedCommand
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Snapshot PowerShell process failed with exit code [$LASTEXITCODE]. Output: $($output -join [Environment]::NewLine)"
|
|
}
|
|
|
|
$json = ($output | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) -join [Environment]::NewLine
|
|
if ([string]::IsNullOrWhiteSpace($json)) {
|
|
throw 'Snapshot PowerShell process returned no JSON output.'
|
|
}
|
|
|
|
$json | ConvertFrom-Json
|
|
}
|
|
|
|
function New-TestBgwMergeSnapshotScript {
|
|
param(
|
|
[string]$ConfigurationDataPath,
|
|
[switch]$BuildLocalBaseline
|
|
)
|
|
|
|
$escapedBgwRoot = $BgwRoot.Replace("'", "''")
|
|
$escapedMergeModulePath = $MergeModulePath.Replace("'", "''")
|
|
$escapedResolveModulePath = $ResolveModulePath.Replace("'", "''")
|
|
$escapedConfigurationDataPath = if ($ConfigurationDataPath) { $ConfigurationDataPath.Replace("'", "''") } else { '' }
|
|
$buildLocalBaselineText = if ($BuildLocalBaseline) { 'True' } else { 'False' }
|
|
|
|
@"
|
|
`$ErrorActionPreference = 'Stop'
|
|
`$ProgressPreference = 'SilentlyContinue'
|
|
Import-Module '$escapedMergeModulePath' -Force
|
|
Import-Module '$escapedResolveModulePath' -Force
|
|
|
|
function ConvertTo-SnapshotStableValue {
|
|
param([object]`$Value)
|
|
|
|
if (`$null -eq `$Value) { return `$null }
|
|
if (`$Value -is [string] -or `$Value -is [bool] -or `$Value -is [int] -or `$Value -is [long] -or `$Value -is [double] -or `$Value -is [decimal]) { return `$Value }
|
|
|
|
if (`$Value -is [System.Collections.IDictionary]) {
|
|
`$result = [ordered]@{}
|
|
foreach (`$key in @(`$Value.Keys | Sort-Object)) {
|
|
`$result[[string]`$key] = ConvertTo-SnapshotStableValue -Value `$Value[`$key]
|
|
}
|
|
Write-Output -NoEnumerate `$result
|
|
return
|
|
}
|
|
|
|
if (`$Value -is [pscustomobject]) {
|
|
`$result = [ordered]@{}
|
|
foreach (`$property in @(`$Value.PSObject.Properties | Sort-Object Name)) {
|
|
`$result[`$property.Name] = ConvertTo-SnapshotStableValue -Value `$property.Value
|
|
}
|
|
Write-Output -NoEnumerate `$result
|
|
return
|
|
}
|
|
|
|
if (`$Value -is [System.Collections.IEnumerable] -and `$Value -isnot [string]) {
|
|
`$items = New-Object System.Collections.Generic.List[object]
|
|
foreach (`$item in `$Value) {
|
|
`$items.Add((ConvertTo-SnapshotStableValue -Value `$item))
|
|
}
|
|
Write-Output -NoEnumerate `$items.ToArray()
|
|
return
|
|
}
|
|
|
|
return `$Value
|
|
}
|
|
|
|
function ConvertTo-SnapshotStableJson {
|
|
param([object]`$Value)
|
|
(ConvertTo-SnapshotStableValue -Value `$Value) | ConvertTo-Json -Depth 100 -Compress
|
|
}
|
|
|
|
if ('$buildLocalBaselineText' -eq 'True') {
|
|
`$root = '$escapedBgwRoot'
|
|
`$sourceTemplates = @(
|
|
(Join-Path -Path `$root -ChildPath 'Environment\Test.psd1')
|
|
(Join-Path -Path `$root -ChildPath 'Domain\Contoso.psd1')
|
|
(Join-Path -Path `$root -ChildPath 'Service\SharePoint\Contoso.psd1')
|
|
(Join-Path -Path `$root -ChildPath 'Stage\Install.psd1')
|
|
)
|
|
|
|
`$allNodes = @(
|
|
@{ NodeName = 'CLD-SHP-01'; RunCentralAdministration = `$true }
|
|
@{ NodeName = 'CLD-SHP-02'; RunCentralAdministration = `$false }
|
|
@{ NodeName = 'CLD-SHP-03'; RunCentralAdministration = `$false }
|
|
)
|
|
|
|
`$configurationData = New-DSCConfigurationDataDeployment -Name 'Contoso-Test-SharePoint' -DeploymentId '8f6c2c1a' -SourceTemplatePath `$sourceTemplates -AllNodes `$allNodes
|
|
}
|
|
else {
|
|
`$configurationData = Import-PowerShellDataFile -LiteralPath '$escapedConfigurationDataPath'
|
|
}
|
|
|
|
`$resolved = Resolve-DSCConfigurationData -ConfigurationData `$configurationData -SkipSecrets
|
|
`$snapshot = [ordered]@{
|
|
ParametersJson = ConvertTo-SnapshotStableJson -Value `$configurationData.Parameters
|
|
VariablesJson = ConvertTo-SnapshotStableJson -Value `$configurationData.Variables
|
|
ResourcesJson = ConvertTo-SnapshotStableJson -Value `$configurationData.Resources
|
|
DatabasePrefix = `$resolved.Variables.DatabasePrefix
|
|
ServiceDbPrefix = `$resolved.Variables.ServiceDbPrefix
|
|
ConfigDbName = `$resolved.Variables.ConfigDbName
|
|
LcmConfigurationMode = `$resolved.Resources.NonNodeData.LocalConfigurationManager.ConfigurationMode
|
|
AllNodes = @(`$resolved.Resources.AllNodes | Sort-Object NodeName | ForEach-Object {
|
|
[ordered]@{
|
|
NodeName = `$_.NodeName
|
|
RunCentralAdministration = `$_.RunCentralAdministration
|
|
}
|
|
})
|
|
}
|
|
|
|
`$snapshot | ConvertTo-Json -Depth 100 -Compress
|
|
"@
|
|
}
|
|
|
|
function New-TestBgwMergeLocalSnapshot {
|
|
Invoke-TestBgwMergeSnapshotScript -Script (New-TestBgwMergeSnapshotScript -BuildLocalBaseline)
|
|
}
|
|
|
|
function New-TestBgwMergeSnapshotFromConfigurationData {
|
|
param([hashtable]$ConfigurationData)
|
|
|
|
Import-Module $MergeModulePath -Force
|
|
|
|
$tempPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ("BgwMergeApiSnapshot-{0}.psd1" -f ([guid]::NewGuid().ToString('N')))
|
|
try {
|
|
Export-PowerShellDataFile -InputObject $ConfigurationData -Path $tempPath -Force
|
|
Invoke-TestBgwMergeSnapshotScript -Script (New-TestBgwMergeSnapshotScript -ConfigurationDataPath $tempPath)
|
|
}
|
|
finally {
|
|
if (Test-Path -LiteralPath $tempPath) {
|
|
Remove-Item -LiteralPath $tempPath -Force
|
|
}
|
|
}
|
|
}
|
|
|
|
function New-TestBgwMergeApiConfigurationData {
|
|
param([object]$Composition)
|
|
|
|
Import-Module $MergeModulePath -Force
|
|
|
|
$mergedTemplateData = @{}
|
|
|
|
foreach ($selection in @($Composition.templateSelections | Sort-Object sortOrder)) {
|
|
$templateData = ConvertFrom-TestBgwMergeTemplateJson -JsonData $selection.templateVersion.jsonData
|
|
$mergedTemplateData = Merge-DSCConfigurationData -Template $mergedTemplateData -Deployment $templateData
|
|
}
|
|
|
|
$deploymentData = @{
|
|
Resources = @{
|
|
AllNodes = ConvertFrom-TestBgwMergeTargetAssignments -TargetAssignments @($Composition.targetAssignments)
|
|
}
|
|
}
|
|
|
|
Merge-DSCConfigurationData -Template $mergedTemplateData -Deployment $deploymentData
|
|
}
|
|
|
|
Describe 'BGW Test.Merge.ps1 API DemoData composition' {
|
|
BeforeAll {
|
|
$script:expectedTemplateAliases = @(
|
|
'Environment-Test'
|
|
'Domain-Contoso'
|
|
'Service-SharePoint-Contoso'
|
|
'Stage-Install'
|
|
)
|
|
|
|
$script:expectedTemplateRoles = @(
|
|
'Environment'
|
|
'Domain'
|
|
'Service'
|
|
'Stage'
|
|
)
|
|
|
|
$script:localSnapshot = New-TestBgwMergeLocalSnapshot
|
|
$script:apiComposition = Invoke-TestBgwMergeApiJson -Method GET -Path "deployment-batches/$DeploymentBatchId/composition"
|
|
$script:apiConfigurationData = New-TestBgwMergeApiConfigurationData -Composition $script:apiComposition
|
|
$script:apiSnapshot = New-TestBgwMergeSnapshotFromConfigurationData -ConfigurationData $script:apiConfigurationData
|
|
}
|
|
|
|
It 'loads the seeded deployment composition from the API' {
|
|
$script:apiComposition.deploymentBatchId | Should Be $DeploymentBatchId
|
|
@($script:apiComposition.templateSelections).Count | Should Be 4
|
|
@($script:apiComposition.targetAssignments).Count | Should Be 3
|
|
}
|
|
|
|
It 'keeps the template selection order from Test.Merge.ps1' {
|
|
$aliases = @($script:apiComposition.templateSelections | Sort-Object sortOrder | ForEach-Object { $_.alias })
|
|
$roles = @($script:apiComposition.templateSelections | Sort-Object sortOrder | ForEach-Object { $_.templateRole })
|
|
|
|
($aliases -join '|') | Should Be ($script:expectedTemplateAliases -join '|')
|
|
($roles -join '|') | Should Be ($script:expectedTemplateRoles -join '|')
|
|
}
|
|
|
|
It 'keeps the AllNodes data from Test.Merge.ps1' {
|
|
$nodes = @($script:apiSnapshot.AllNodes | Sort-Object NodeName)
|
|
|
|
$nodes.Count | Should Be 3
|
|
$nodes[0].NodeName | Should Be 'CLD-SHP-01'
|
|
$nodes[0].RunCentralAdministration | Should Be $true
|
|
$nodes[1].NodeName | Should Be 'CLD-SHP-02'
|
|
$nodes[1].RunCentralAdministration | Should Be $false
|
|
$nodes[2].NodeName | Should Be 'CLD-SHP-03'
|
|
$nodes[2].RunCentralAdministration | Should Be $false
|
|
}
|
|
|
|
It 'resolves the same core values as the local Test.Merge.ps1 baseline' {
|
|
$script:apiSnapshot.DatabasePrefix | Should Be $script:localSnapshot.DatabasePrefix
|
|
$script:apiSnapshot.ServiceDbPrefix | Should Be $script:localSnapshot.ServiceDbPrefix
|
|
$script:apiSnapshot.ConfigDbName | Should Be $script:localSnapshot.ConfigDbName
|
|
$script:apiSnapshot.LcmConfigurationMode | Should Be $script:localSnapshot.LcmConfigurationMode
|
|
}
|
|
|
|
It 'matches merged Parameters, Variables and Resources' {
|
|
$script:apiSnapshot.ParametersJson | Should Be $script:localSnapshot.ParametersJson
|
|
$script:apiSnapshot.VariablesJson | Should Be $script:localSnapshot.VariablesJson
|
|
$script:apiSnapshot.ResourcesJson | Should Be $script:localSnapshot.ResourcesJson
|
|
}
|
|
}
|
|
|