Skip to content
This repository was archived by the owner on Sep 4, 2020. It is now read-only.

Hashtable support #201

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 183 additions & 53 deletions Vester/Private/Template/VesterTemplate.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,99 @@ Param(
[switch]$Remediate
)

function ConvertPSObjectToHashtable {
param (
[Parameter(ValueFromPipeline)]
$InputObject
)

process {
if ($InputObject -is [psobject]){
$hash = @{}
foreach ($property in $InputObject.PSObject.Properties){
$hash[$property.Name] = ConvertPSObjectToHashtable $property.Value
}
$hash
}
else{
$InputObject
}
}
}

function Compare-Hashtable {
<#
.SYNOPSIS
Compare two Hashtable and returns an array of differences.

.DESCRIPTION
The Compare-Hashtable function computes differences between two Hashtables. Results are returned as
an array of objects with the properties: "key" (the name of the key that caused a difference),
"side" (one of "<=", "!=" or "=>"), "lvalue" an "rvalue" (resp. the left and right value
associated with the key).

.PARAMETER left
The left hand side Hashtable to compare.

.PARAMETER right
The right hand side Hashtable to compare.

.EXAMPLE

Returns a difference for ("3 <="), c (3 "!=" 4) and e ("=>" 5).

Compare-Hashtable @{ a = 1; b = 2; c = 3 } @{ b = 2; c = 4; e = 5}

.EXAMPLE

Returns a difference for a ("3 <="), c (3 "!=" 4), e ("=>" 5) and g (6 "<=").

$left = @{ a = 1; b = 2; c = 3; f = $Null; g = 6 }
$right = @{ b = 2; c = 4; e = 5; f = $Null; g = $Null }

Compare-Hashtable $left $right

#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[Hashtable]$Left,

[Parameter(Mandatory = $true)]
[Hashtable]$Right
)

function New-Result($Key, $LValue, $Side, $RValue) {
New-Object -Type PSObject -Property @{
key = $Key
lvalue = $LValue
rvalue = $RValue
side = $Side
}
}
[Object[]]$Results = $Left.Keys | % {
if ($Left.ContainsKey($_) -and !$Right.ContainsKey($_)) {
New-Result $_ $Left[$_] "<=" $Null
} else {
if ($Left[$_] -is [hashtable] -and $Right[$_] -is [hashtable] ) {
Compare-Hashtable $Left[$_] $Right[$_]
}
else {
$LValue, $RValue = $Left[$_], $Right[$_]
if ($LValue -ne $RValue) {
New-Result $_ $LValue "!=" $RValue
}
}
}
}
$Results += $Right.Keys | % {
if (!$Left.ContainsKey($_) -and $Right.ContainsKey($_)) {
New-Result $_ $Null "=>" $Right[$_]
}
}
if ($Results -ne $null) { $Results }
}

# Gets the scope, the objects for the scope and their requested test files
$Scopes = Split-Path (Split-Path $TestFiles -Parent) -Leaf | Select -Unique
$Final = @()
Expand Down Expand Up @@ -74,60 +167,97 @@ foreach($Scope in $Final.Scope)
# Runs through each test file on the below objects in the current scope
foreach($Test in $Tests)
{
Write-Verbose "Processing test file $Test"
$TestName = Split-Path $Test -Leaf

Describe -Name "$Scope Configuration: $TestName" -Fixture {
# Pull in $Title/$Description/$Desired/$Type/$Actual/$Fix from the test file
. $Test

# Pump the brakes if the config value is $null
If ($Desired -eq $null) {
Write-Verbose "Due to null config value, skipping test $TestName"
# Use continue to skip this test and go to the next loop iteration
continue
}

# Loops through each object in the inventory list for the specific scope.
# It runs one test at a time against each $Object and moves onto the next test.
foreach($Object in $Inventory)
{
It -Name "$Scope $($Object.Name) - $Title" -Test {
Try {
# "& $Actual" is running the first script block to compare to $Desired
# The comparison should be empty
# (meaning everything is the same, as expected)
$Result = (& $Actual -as $Type)
#allow for $Desired to be a scriptblock vs simple value
if ($Desired.GetType().name -eq 'scriptblock') {
$Desired = (& $Desired -As $Type)
}
Compare-Object -ReferenceObject $Desired -DifferenceObject $Result |
Should BeNullOrEmpty
} Catch {
# If the comparison found something different,
# Then check if we're going to fix it
If ($Remediate) {
Write-Warning -Message $_
# -WhatIf support wraps the command that would change values
If ($PSCmdlet.ShouldProcess("vCenter '$($cfg.vcenter.vc)' - $Scope '$Object'", "Set '$Title' value to '$Desired'")) {
Write-Warning -Message "Remediating $Object"
# Execute the $Fix script block
& $Fix
}
} Else {
# -Remediate is not active, so just report the error
$Message = @(
"Desired: [$($Desired.gettype())] $Desired"
"Actual: [$($Result.gettype())] $Result"
"Synopsis: $Description"
"Link: https://wahlnetwork.github.io/Vester/reference/tests/$Scope/$($Title.replace(' ','-').replace(':','')).html"
"Test File: $Test"
) -join "`n"
Throw $Message
}
} #Try/Catch
} #It
# Loops through each object in the inventory list for the specific scope.
# It runs one test at a time against each $Object and moves onto the next test.
ForEach($Object in $Inventory)
{
Write-Verbose "Processing test file $Test"
$TestName = Split-Path $Test -Leaf

Describe -Name "$Scope Configuration: $TestName" -Fixture {
# Pull in $Title/$Description/$Desired/$Type/$Actual/$Fix from the test file
. $Test

# If multiple tests
# Added in a check for $NULL and "" as you can't run a method (gettype()) on a $Null valued expression
# This is checking for an object that is a PSCustomobject, which shouldnt be null.
If(($Desired -ne $NULL) -and ($Desired -ne "") -and ($Desired.GetType().Name -eq "PSCustomObject")) {
# Gathers the the actual and desired values
# Formats the hashtable as a PSCustomObject
# As a side note: The ActualObjects Type is always the correct object type
# ConvertFrom-Json does not preserve things like 'int64'
# Converts to hashtable
$Results = (& $Actual)

# Converts $Desired to a hashtable as it needs to be a hashtable to be compared
# $ht2 = @{}
# $Desired.psobject.properties | Foreach { $ht2[$_.Name] = $_.Value }
# $Desired = $ht2
$Desired = $Desired | ConvertPSObjectToHashtable

It -Name "$Scope $($Object.Name) - $Title" -Test {
Try {
$Mishaps = Compare-HashTable -Left $Desired -Right $Results
$Mishaps | Should BeNullOrEmpty
} Catch {
# If the comparison found something different,
# Then check if we're going to fix it
If($Remediate) {
Write-Warning -Message $_
# -WhatIf support wraps the command that would change values
If($PSCmdlet.ShouldProcess("vCenter '$($cfg.vcenter.vc)' - $Scope '$Object'", "Set '$Title' value to '$Desired'"))
{
Write-Warning -Message "Remediating $Object"
# Execute the $Fix script block
& $Fix
}
} Else {
# -Remediate is not active, so just report the error
### Changed it to write-error so it didnt terminate the above foreach loop
#Write-Error "$($_.Exception.Message)" -ErrorAction Stop
Write-Error "$($_.Exception.Message)`n$($Mishaps | Convertto-json)" -ErrorAction "Stop"
}
} # Try/Catch
} # It
} # If $Desired -eq PSCustomobject
# Else it is a normal single value test
Else
{
It -Name "$Scope $($Object.Name) - $Title" -Test {
Try {
# Checks for $NULLs
If($Desired -eq $NULL) {
Write-Verbose "Making sure `$Null is still `$Null"
($Desired -eq (& $Actual -as $Type)) -or ("" -eq (& $Actual -as $Type)) | Should Be $TRUE
} Else {
Compare-Object -ReferenceObject $Desired -DifferenceObject (& $Actual -as $Type) | Should BeNullOrEmpty
}
} Catch {
# If the comparison found something different,
# Then check if we're going to fix it
If ($Remediate) {
Write-Warning -Message $_
# -WhatIf support wraps the command that would change values
If ($PSCmdlet.ShouldProcess("vCenter '$($cfg.vcenter.vc)' - $Scope '$Object'", "Set '$Title' value to '$Desired'")) {
Write-Warning -Message "Remediating $Object"
# Execute the $Fix script block
& $Fix
}
} Else {
# -Remediate is not active, so just report the error
$Message = @(
"Desired: [$($Desired.gettype())] $Desired"
"Actual: [$($Result.gettype())] $Result"
"Synopsis: $Description"
"Link: https://wahlnetwork.github.io/Vester/reference/tests/$Scope/$($Title.replace(' ','-').replace(':','')).html"
"Test File: $Test"
) -join "`n"
Throw $Message
}
} #Try/Catch
} #It
} #If/Else $Desired.GetType().Name -eq "PSCustomObject"
} #Foreach Inventory
}#Describe
}#Foreach Tests
Expand Down
29 changes: 29 additions & 0 deletions Vester/Tests/Host/AD-JoinDomain.Vester.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Test file for the Vester module - https://github.com/WahlNetwork/Vester
# Called via Invoke-Pester VesterTemplate.Tests.ps1

# Test title, e.g. 'DNS Servers'
$Title = 'Join Domain'

# Test description: How New-VesterConfig explains this value to the user
$Description = 'Join ESXi hosts to an Active Directory (AD) domain to eliminate the need to create and maintain multiple local user accounts.'

# The config entry stating the desired values
$Desired = $cfg.host.joindomain

# The test value's data type, to help with conversion: bool/string/int
$Type = 'string'

# The command(s) to pull the actual value for comparison
# $Object will scope to the folder this test is in (Cluster, Host, etc.)
[ScriptBlock]$Actual = {
($Object | Get-VMHostAuthentication).Domain
}

# The command(s) to match the environment to the config
# Use $Object to help filter, and $Desired to set the correct value
[ScriptBlock]$Fix = {
if (($Object | Get-VMHostAuthentication).Domain -ne $null){
$Object | Get-VMHostAuthentication | Set-VMHostAuthentication -LeaveDomain -Force -Confirm:$false
}
$Object | Get-VMHostAuthentication | Set-VMHostAuthentication -JoinDomain -Domain $Desired -Credential (Get-Credential -Message "Please enter privileged credential for join domain $Desired") -Confirm:$false
}
32 changes: 32 additions & 0 deletions Vester/Tests/Host/DCUI-Service-Policy.Vester.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Test file for the Vester module - https://github.com/WahlNetwork/Vester
# Called via Invoke-Pester VesterTemplate.Tests.ps1

# Test title, e.g. 'DNS Servers'
$Title = 'DCUI Service Policy'

# Test description: How New-VesterConfig explains this value to the user
$Description = 'Policy for DCUI service (on,off,automatic)'

# The config entry stating the desired values
$Desired = $cfg.host.dcuiservicepolicy

# The test value's data type, to help with conversion: bool/string/int
$Type = 'string'

# The command(s) to pull the actual value for comparison
# $Object will scope to the folder this test is in (Cluster, Host, etc.)
[ScriptBlock]$Actual = {
($Object | Get-VMHostService | Where-Object -FilterScript {
$_.Key -eq 'DCUI'
}).Policy
}

# The command(s) to match the environment to the config
# Use $Object to help filter, and $Desired to set the correct value
[ScriptBlock]$Fix = {
Set-VMHostService -HostService ($Object |
Get-VMHostService |
Where-Object -FilterScript {
$_.Key -eq 'DCUI'
}) -Policy $Desired -ErrorAction Stop
}
43 changes: 43 additions & 0 deletions Vester/Tests/Host/DCUI-Service.Vester.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Test file for the Vester module - https://github.com/WahlNetwork/Vester
# Called via Invoke-Pester VesterTemplate.Tests.ps1

# Test title, e.g. 'DNS Servers'
$Title = 'DCUI Service State'

# Test description: How New-VesterConfig explains this value to the user
$Description = 'Checks state of DCUI service (running or stopped)'

# The config entry stating the desired values
$Desired = $cfg.host.dcuiservicerunning

# The test value's data type, to help with conversion: bool/string/int
$Type = 'bool'

# The command(s) to pull the actual value for comparison
# $Object will scope to the folder this test is in (Cluster, Host, etc.)
[ScriptBlock]$Actual = {
($Object | Get-VMHostService | Where-Object -FilterScript {
$_.Key -eq 'DCUI'
}).Running
}

# The command(s) to match the environment to the config
# Use $Object to help filter, and $Desired to set the correct value
[ScriptBlock]$Fix = {
if ($Desired -eq $true)
{
Start-VMHostService -HostService ($Object |
Get-VMHostService |
Where-Object -FilterScript {
$_.Key -eq 'DCUI'
}) -ErrorAction Stop -Confirm:$false
}
if ($Desired -eq $false)
{
Stop-VMHostService -HostService ($Object |
Get-VMHostService |
Where-Object -FilterScript {
$_.Key -eq 'DCUI'
}) -ErrorAction Stop -Confirm:$false
}
}
Loading