Private/Template/VesterTemplate.Tests.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
<#
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'    {$Datacenter | Get-Cluster -Name $cfg.scope.cluster}
        'DSCluster'  {$Datacenter | Get-DatastoreCluster -Name $cfg.scope.dscluster}
        'Host'       {$Datacenter | Get-Cluster -Name $cfg.scope.cluster | Get-VMHost -Name $cfg.scope.host}
        'VM'         {$Datacenter | Get-Cluster -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)
    {
        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)
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
            } #Foreach Inventory
        }#Describe
    }#Foreach Tests
}#Foreach Final.Scope