DSCResources/POSHOrigin_vSphere_VM/Provisioners/Chef/Test.ps1

[cmdletbinding()]
param(
    [parameter(mandatory)]
    $Options
)

begin {
    Write-Debug -Message 'Chef provisioner test: beginning'
}

process {
    # Test to see if the Chef client is already installed

    $provOptions = ConvertFrom-Json -InputObject $Options.Provisioners
    $chefOptions = ($provOptions | Where-Object {$_.name -eq 'chef'}).options

    $result = $false
    try {

        # Get target IP address
        $t = Get-VM -Id $options.vm.Id -Verbose:$false -Debug:$false
        #$ip = $t.Guest.IPAddress | Where-Object { ($_ -notlike '169.*') -and ( $_ -notlike '*:*') } | Select-Object -First 1
        $ip = _GetGuestVMIPAddress -VM $t

        if ($null -ne $ip -and $ip -ne [string]::Empty) {
            $chefSvc = Invoke-Command -ComputerName $ip -Credential $Options.GuestCredentials -ScriptBlock { Get-Service -Name chef-client -ErrorAction SilentlyContinue } -Verbose:$false
            $chefSvcResult = $true
            if ($chefSvc) {

                # Get the node
                $params = @{
                    Method = 'GET'
                    OrgUri = $chefOptions.url
                    Path = "/nodes/$($chefOptions.NodeName)"
                    UserItem = (Split-Path -Path $chefOptions.clientKey -Leaf).Split('.')[0]
                    KeyPath = $chefOptions.clientKey
                }
                $chefNode = & "$PSScriptRoot\Helpers\_InvokeChefQuery.ps1" @params

                # If we found a node in Chef, do extra validation
                $chefNodeResult = $true
                if ($chefNode) {
                    # Verify environment
                    $envResult = $true
                    if ($chefOptions.environment) {
                        if ($chefNode.chef_environment.ToLower() -ne $chefOptions.environment.ToLower()) {
                            Write-Verbose -Message "Chef environment: MISMATCH [$($chefNode.chef_environment.ToLower()) <> $($chefOptions.environment.ToLower()))]"
                            $envResult = $false
                        }
                    }
                    if ($envResult) {
                        Write-Verbose -Message "Chef environment: MATCH"
                    }

                    # Verify run list matches
                    $runlistResult = $true
                    if (-not $chefOptions.runlist -and $chefOptions.runList.Count -ne 0) {
                        $chefOptions | Add-Member -MemberType NoteProperty -Name runlist -Value @()
                    }
                    if ($chefOptions.runlist.Count -gt 0) {
                        # Create a string array of our runlist so we can easily compare it
                        # to what we get back from the Chef API
                        $refRunList = @($chefOptions.runlist) | ForEach-Object {
                            if ($_.recipe) {
                                "recipe[$($_.recipe)]"
                            } elseif ($_.role) {
                                "role[$($_.role)]"
                            }
                        }
                        $diffRunList = @($chefNode.run_list)
                        if ($diffRunList.Count -gt 0) {
                            # Compare the Chef node runlist to what our desired runlist is
                            if (Compare-Object -ReferenceObject $refRunList -DifferenceObject $diffRunList) {
                                $runlistResult = $false
                            }
                        } else {
                            # The Chef node has no runlist but should
                            $runlistResult = $false
                        }
                    } else {
                        # Our desired runlist is nothing. Check if Chef node has a run list
                        if ($chefNode.run_list.count -gt 0) {
                            $runlistResult = $false
                        }
                    }
                    if (-Not $runListResult) {
                        Write-Verbose -Message "Chef runlist: MISMATCH"
                    } else {
                        Write-Verbose -Message "Chef runlist: MATCH"
                    }

                    # Verify attributes
                    # If we didn't specify any desired attributes, create an empty set
                    # so we can compare it against Chef
                    # Chef node attributes usually have an empty tags attributes by default
                    # so add that to the reference if isn't doesn't already exist
                    $attributeResult = $true
                    if (-Not $ChefOptions.attributes) {
                        $chefOptions | Add-Member -MemberType NoteProperty -Name attributes -Value @{tags = @()}
                    } else {
                        if (-Not $ChefOptions.attributes.tags) {
                            $chefOptions.attributes | Add-Member -MemberType NoteProperty -Name tags -Value @()
                        }
                    }
                    $refJson = $chefOptions.attributes | ConvertTo-Json
                    $diffJson = $chefNode.normal | ConvertTo-Json

                    Write-Debug -Message 'Ref'
                    Write-Debug -Message $refJson
                    Write-Debug -Message 'Diff'
                    Write-Debug -Message $diffJson

                    if ($diffJson -ne $refJson) {
                        Write-Verbose -Message "Chef attributes: MISMATCH"
                        $attributeResult = $false
                    } else {
                        Write-Verbose -Message "Chef attributes: MATCH"
                    }

                    $automateCmd = {
                        $result = $true
                        $chefOptions = $args[0]
                        $automateUrl = $chefOptions.automateUrl
                        $automateToken = $chefOptions.automateToken
                        $automateCert = $chefOptions.automateCert
                        $automateCertName = $automateCert.split('/') | Select-Object -Last 1
                        $testExists = Test-Path "C:\chef\trusted_certs\$automateCertName"
                        if (!($testExists)) {
                            $result = $false
                        } else {
                            Invoke-WebRequest -Uri "$automateCert" -OutFile "C:\chef\$automateCertName" | Out-Null
                            $serverVersion = Get-Content "C:\chef\trusted_certs\$automateCertName"
                            $currentVersion = Get-Content "C:\chef\$automateCertName"
                            $compare = Compare-Object $serverVersion $currentVersion
                            Start-Sleep -Seconds 1
                            Remove-Item -Path "C:\chef\$automateCertName" -Force -Confirm:$false
                            if ($compare) {
                                $result = $false
                            }
                        }
                        $clientRB = Get-Content C:\chef\client.rb -ErrorAction SilentlyContinue | Out-String
                        $autoUrl = "(.*)data_collector.server_url\s+'$automateUrl(.*)'"
                        $autoToken = "(.*)data_collector.token\s+'$automateToken(.*)'"
                        if (($clientRB -notmatch $autoUrl) -or ($clientRB -notmatch $autoToken)) {
                            $result = $false
                        }
                        return $result
                    }
                    $automateParams = @{
                        ComputerName = $ip
                        Credential = $Options.GuestCredentials
                        ScriptBlock = $automateCmd
                        ArgumentList = $chefOptions
                    }
                    if ($chefOptions.automateUrl) {
                        $testAutomate = Invoke-Command @automateParams
                        if (!($testAutomate)) {
                            $chefNodeResult = $false
                            Write-Verbose -Message 'Chef automate settings: MISMATCH'
                        } else {
                            Write-Verbose -Message 'Chef automate settings: MATCH'
                        }
                    }
                } else {
                    $chefNodeResult = $false
                    Write-Verbose -Message 'Chef client: installed but node could not be found on Chef server'
                }
            } else {
                Write-Verbose -Message 'Chef client: not found'
                $chefSvcResult = $false
            }
        } else {
            throw 'No valid IP address returned from VM view. Can not test for Chef client'
        }

        $result = ($chefSvcResult -and $chefNodeResult -and $envResult -and $runlistResult -and $attributeResult)
        $match = if ($result) { 'MATCH' } else { 'MISMATCH' }
        Write-Verbose -Message "Chef provisioner: $match"
        return $result
    } catch {
        Write-Error -Message 'There was a problem testing for the Chef client'
        Write-Error -Message "$($_.InvocationInfo.ScriptName)($($_.InvocationInfo.ScriptLineNumber)): $($_.InvocationInfo.Line)"
        write-Error $_
        return $false
    }
}

end {
    Write-Debug -Message 'Chef provisioner test: ending'
}