Skip to content

Instantly share code, notes, and snippets.

@midacts
Last active December 22, 2017 22:12
Show Gist options
  • Save midacts/c243b64027fc96c66d509ee6744d84a7 to your computer and use it in GitHub Desktop.
Save midacts/c243b64027fc96c66d509ee6744d84a7 to your computer and use it in GitHub Desktop.
# 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 = 'NFS Advanced Settings'
# Test description: How New-VesterConfig explains this value to the user
$Description = "Specifies the NFS settings for the host"
# The config entry stating the desired values
$Desired = $cfg.host.nfssettings
# The test value's data type, to help with conversion: bool/string/int
$Type = 'hashtable'
# 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 = {
$All = [ordered]@{}
$NFS = Get-AdvancedSetting -Entity $Object | Where-Object {$_.Name -Match "NFS"} | Sort Name | Select Name,Value
$NFS | Foreach-Object { $All.Add($_.Name, $_.Value) }
$All
}
# The command(s) to match the environment to the config
# Use $Object to help filter, and $Desired to set the correct value
[ScriptBlock]$Fix = {
ForEach ( $Mishap in $Mishaps )
{
Get-AdvancedSetting -Entity $Object -Name $Mishap.Key | Set-AdvancedSetting -Value $Mishap.LValue -Confirm:$False
}
}
<#
This file exists to combine simple user input (Invoke-Vester), simple
user test authoring (*.Vester.ps1), and properly scoped inventory objects
into a single test session that loops through all necessary combinations.
It is called by Invoke-Vester via the Invoke-Pester command.
https://wahlnetwork.github.io/Vester
#>
# Accept -WhatIf input from Invoke-Vester
[CmdletBinding(SupportsShouldProcess = $true,
ConfirmImpact = 'Medium')]
Param(
# The $cfg hashtable from a single config file
[object]$Cfg,
# Array of paths for tests to run against this config file
[object]$TestFiles,
# Pass through the user's preference to fix differences or not
[switch]$Remediate
)
# Gets the scope, the objects for the scope and their requested test files
$Scopes = Split-Path (Split-Path $TestFiles -Parent) -Leaf | Select -Unique
$Final = @()
$InventoryList = @()
$Datacenter = Get-Datacenter -Name $cfg.scope.datacenter -Server $cfg.vcenter.vc
foreach($Scope in $Scopes)
{
Write-Verbose "Processing $Scope"
Remove-Variable InventoryList -ErrorAction SilentlyContinue # Makes sure the variable is always fresh
# Use $Scope (parent folder) to get the correct objects to test against
# If changing values here, update the "$Scope -notmatch" test below as well
$InventoryList = switch ($Scope) {
'vCenter' {$global:DefaultVIServer | where-object {$_.name -like "$($cfg.vcenter.vc)"}}
'Datacenter' {$Datacenter}
'Cluster' {Get-Cluster -Location $Datacenter -Name $cfg.scope.cluster}
'DSCluster' {$Datacenter | Get-DatastoreCluster -Name $cfg.scope.dscluster}
'Host' {Get-Cluster -Location $Datacenter -Name $cfg.scope.cluster | Get-VMHost -Name $cfg.scope.host}
'VM' {Get-Cluster -Location $Datacenter -Name $cfg.scope.cluster | Get-VM -Name $cfg.scope.vm}
'Network' {$Datacenter | Get-VDSwitch -Name $cfg.scope.vds}
}
$ScopeObj = [pscustomobject] @{
'Scope' = $Scope
'InventoryList' = $InventoryList
'TestFiles' = $TestFiles | Where-Object { (Split-Path (Split-Path $_ -Parent) -Leaf) -eq $Scope }
}
if (($ScopeObj.InventoryList -ne $NULL) -and ($ScopeObj.TestFiles -ne $NULL)){
$Final += $ScopeObj
}
}
# Loops through each Scope
foreach($Scope in $Final.Scope)
{
# Pulling the inventory and test files for this scope
$Inventory = ($Final | Where-Object { $_.Scope -eq $Scope }).InventoryList
$Tests = ($Final | Where-Object { $_.Scope -eq $Scope }).TestFiles
# The parent folder must be one of these names, to help with $Object scoping below
# If adding here, also needs to be added to the switch below
If ('vCenter|Datacenter|Cluster|DSCluster|Host|VM|Network' -notmatch $Scope) {
Write-Warning "Skipping test $TestName. Use -Verbose for more details"
Write-Verbose 'Test files should be in a folder with one of the following names:'
Write-Verbose 'vCenter / Datacenter / Cluster / DSCluster / Host / VM / Network'
Write-Verbose 'This helps Vester determine which inventory object(s) to use during the test.'
# Use continue to skip this test and go to the next loop iteration
continue
}
# Runs through each test file on the below objects in the current scope
foreach($Test in $Tests)
{
# 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
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
}#Foreach Final.Scope
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment