DSCClassResources/QlikNode.psm1

$ProjectRoot = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $ProjectRoot -ChildPath 'Private' | Join-Path -ChildPath 'Common.psm1') -Force
Import-Module (Join-Path $ProjectRoot -ChildPath 'Private' | Join-Path -ChildPath 'Bootstrap.psm1') -Force

enum Ensure {
    Absent
    Present
}

[DscResource()]
class QlikNode{

    [DscProperty(Key)]
    [string]
    $HostName

    [DscProperty()]
    [string]
    $Name

    [DscProperty()]
    [string]
    $NodePurpose

    [DscProperty()]
    [hashtable]
    $CustomProperties

    [DscProperty()]
    [string[]]
    $Tags

    [DscProperty()]
    [Nullable[bool]]
    $Engine

    [DscProperty()]
    [Nullable[bool]]
    $Proxy

    [DscProperty()]
    [Nullable[bool]]
    $Scheduler

    [DscProperty()]
    [Nullable[bool]]
    $Printing

    [DscProperty()]
    [Nullable[bool]]
    $Failover

    [DscProperty(Mandatory)]
    [Ensure]
    $Ensure

    [void] Set() {
        $item = Get-QlikNode -full -filter "hostName eq '$($this.HostName)'"
        $present = $null -ne $item
        $Bootstrap = $null

        if ($this.ensure -eq [Ensure]::Present) {
            $params = @{
                engineEnabled = $this.Engine
                proxyEnabled = $this.Proxy
                schedulerEnabled = $this.Scheduler
                printingEnabled = $this.Printing
                failoverCandidate = $this.Failover
            }
            if ($this.Name) { $params.Add("Name", $this.Name) }
            if ($this.NodePurpose) { $params.Add("NodePurpose", $this.NodePurpose) }

            $props = ConfigurePropertiesAndTags($this)
            if ($props.CustomProperties) { $params.Add("CustomProperties", $props.CustomProperties)}
            if ($props.Tags) { $params.Add("Tags", $props.Tags)}

            if ($present) {
                if (-not $this.hasProperties($item)) {
                    Update-QlikNode -id $item.id @params
                }

                if ((Get-Service QlikSenseRepositoryService).Status -ne 'Running') {
                    $Bootstrap = Start-SenseBootstrap -Service Repository
                }
                else {
                    $counter = 0
                    while (Get-QlikServiceStatus -full -filter "serverNodeConfiguration.id eq $($item.id) and serviceType eq Repository and serviceState eq NoCommunication") {
                        $counter++
                        if ($counter -gt 20) { throw "Repository service status is NoCommunication" }
                        Start-Sleep -Seconds 15
                    }
                }

                if ($state = Get-QlikServiceStatus -full -filter "serverNodeConfiguration.id eq $($item.id) and serviceType eq Repository and serviceState ne Running") {
                    Write-Verbose "Repository service status is $($state.serviceState)"
                    $password = Invoke-QlikGet "/qrs/servernoderegistration/start/$($item.id)"
                    if ($password) {
                        Write-Verbose "Unlocking certificates on node"
                        $postParams = @{__pwd = "$password" }
                        Invoke-WebRequest -Uri "http://localhost:4570/certificateSetup" -Method Post -Body $postParams -UseBasicParsing > $null
                    }

                    if ($Bootstrap) {
                        $Bootstrap.WaitForExit()
                        if ($Bootstrap.ExitCode -ne 0) {
                            throw "Bootstrap exited with status $($Bootstrap.ExitCode)"
                        }
                    }
                }
            }
            else {
                if ((Get-Service QlikSenseRepositoryService).Status -ne 'Running') {
                    $Bootstrap = Start-SenseBootstrap -Service Repository
                }
                Register-QlikNode -hostName $this.HostName @params
                if ($Bootstrap) {
                    $Bootstrap.WaitForExit()
                    if ($Bootstrap.ExitCode -ne 0) {
                        throw "Bootstrap exited with status $($Bootstrap.ExitCode)"
                    }
                }
            }
        }
        else {
            Remove-QlikNode $item.id
        }
    }

    [bool] Test() {
        $item = Get-QlikNode -full -filter "hostName eq '$($this.HostName)'"
        $present = $null -ne $item

        if ($this.ensure -eq [Ensure]::Present) {
            if ($present) {
                if ($this.hasProperties($item)) {
                    if ($state = Get-QlikServiceStatus -full -filter "serverNodeConfiguration.id eq $($item.id) and serviceType eq Repository and serviceState ne Running") {
                        Write-Verbose "Repository service status is $($state.serviceState)"
                        return $false
                    }

                    Write-Verbose "Node with hostname of '$($this.HostName)' is present and correct"
                    return $true
                } else {
                    return $false
                }
            } else {
                Write-Verbose "Node not found with hostname of '$($this.HostName)', but should be present"
                return $false
            }
        } else {
            if ($present) {
                Write-Verbose "Node found with hostname of '$($this.HostName)', but should be absent"
                return $false
            }
            else {
                Write-Verbose "Node with hostname of '$($this.HostName)' is in desired state of absent"
                return $true
            }
        }
    }

    [QlikNode] Get() {
        $item = Get-QlikNode -raw -full -filter "hostName eq '$($this.HostName)'"
        $present = $null -ne $item

        if ($present) {
            $this.NodePurpose = $item.NodePurpose
            $this.CustomProperties = $item.CustomProperties
            $this.Tags = $item.Tags
            $this.Engine = $item.EngineEnabled
            $this.Proxy = $item.ProxyEnabled
            $this.Scheduler = $item.SchedulerEnabled
            $this.Printing = $item.PrintingEnabled
            $this.Failover = $item.FailoverCandidate
        }

        return $this
    }

    [bool] hasProperties($item) {
        if (! (CompareProperties $this $item @( 'NodePurpose', 'Tags', 'Name' ))) {
            return $false
        }

        if ($this.CustomProperties) {
            foreach ($defined in $this.CustomProperties) {
                $val = $defined.Split("=")
                $found = $false
                foreach ($exists in $item.customProperties) {
                    if ($exists.definition.name -eq $val[0]) {
                        if ($val[1] -eq "null" -Or $val[1] -ne $exists.value) {
                            Write-Verbose "Test-HasProperties: Custom property value - $($val[0])=$($exists.value) does not match desired state - $($val[1])"
                            return $false
                        }
                        else {
                            $found = $true
                        }
                    }
                }

                if (-not $found) {
                    return $false
                }
            }
        }

        if ($null -ne $this.Engine -and $item.EngineEnabled -ne $this.Engine) {
            Write-Verbose "Test-HasProperties: Engine property value - $($item.EngineEnabled) does not match desired state - $($this.Engine)"
            return $false
        }

        if ($null -ne $this.Proxy -and $item.ProxyEnabled -ne $this.Proxy) {
            Write-Verbose "Test-HasProperties: Proxy property value - $($item.ProxyEnabled) does not match desired state - $($this.Proxy)"
            return $false
        }

        if ($null -ne $this.Scheduler -and $item.SchedulerEnabled -ne $this.Scheduler) {
            Write-Verbose "Test-HasProperties: Scheduler property value - $($item.SchedulerEnabled) does not match desired state - $($this.Scheduler)"
            return $false
        }

        if ($null -ne $this.Printing -and $item.PrintingEnabled -ne $this.Printing) {
            Write-Verbose "Test-HasProperties: Printing property value - $($item.PrintingEnabled) does not match desired state - $($this.Printing)"
            return $false
        }

        if ($null -ne $this.Failover -and $item.failoverCandidate -ne $this.Failover) {
            Write-Verbose "Test-HasProperties: Failover property value - $($item.failoverCandidate) does not match desired state - $($this.Failover)"
            return $false
        }

        return $true
    }
}