Scripts/ExternalSources/Racktables.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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
<#
.SYNOPSIS
    Pull Racktables data, add to Neo4j

.DESCRIPTION
    Pull Racktables data, add to Neo4j

    This is invoked by Connect-TheDots

.PARAMETER Prefix
    Prefix to append to properties when we add them to Neo4j

    This helps identify properties that might come from mutiple sources, or where the source is ambiguous

    For example, row becomes RACKrow

    Defaults to RACK.

.PARAMETER MergeProperty
    We use this to correlate Server data from multiple sources

    We assume server data should correlate if the value for this on a Racktables system matches the ServerUnique value in Neo4j

    Default: NameLower. Change at your own risk

.PARAMETER Label
    What label do we assign the data we pull?

    Defaults to Server. Change at your own risk

.PARAMETER Properties
    Properties to extract and select from Racktables

.PARAMETER Excludes
    Properties to exclude (in line with transforms)

.PARAMETER Transforms
    Properties to select again (in line with excludes)

    Example:

    *, # Keep all properties from -Properties
    @{
        label='NameLower'
        expression={
            @($_.System -split " ")[0].ToLower()
        }
    }

    This is the default. It keeps all properties from -Properties, and add a calculated NameLower

.PARAMETER BaseUri
    Racktables "API" Base URI

    See http://sjoeboo.github.io/blog/2012/05/31/getting-racktables-location-info-into-puppet/ for details

.PARAMETER Domains
    Append these to make Racktables MergeProperty value fully qualified.

    E.g. if Racktables NameLower is dc01, and Domains is contoso.com and contoso2.com,
         we merge data into servers with hostname dc01.contoso.com and/or dc01.contoso2.com

.FUNCTIONALITY
    Dots
#>

[cmdletbinding()]
param(
    [string]$Prefix = 'RACK',
    [string]$MergeProperty = 'NameLower',
    [string]$Label = 'Server',
    [string[]]$Properties = @(
        'System'
        'asset',
        'row',
        'rack',
        'height',
        'ru'
    ),
    [string[]]$Excludes,
    [object[]]$Transforms = @(
        '*',
        @{
            label='NameLower'
            expression={
                @($_.System -split " ")[0].ToLower()
            }
        }
    ),
    [string]$BaseUri,
    [string[]]$Domains,
    [switch]$AllLower = $Script:AllLower
)
# Dot source so module import is available in this scope
if($script:TestMode) {
    Write-Verbose "Using mock functions from $ModuleRoot/Mock/Mocks.ps1"
    . "$ModuleRoot/Mock/Mocks.ps1"
}
else {
    function Get-RackFacts {
        <#
        .SYNOPSIS
            Query RackTables data
        .DESCRIPTION
            Query RackTables data generated via duct tapi:
            http://sjoeboo.github.io/blog/2012/05/31/getting-racktables-location-info-into-puppet/
        .EXAMPLE
            Get-RackFacts -ComputerName cepr*
            # Get all the things that start with cepr
        .EXAMPLE
            Get-RackFacts -ComputerName *
            # Get all the things
        #>

        param (
            [string]$BaseUri,
            [string[]]$ComputerName
        )
        function ConvertFrom-RackFacts {
            [cmdletbinding()]
            param(
                [string]$BaseUri,
                [string]$Name
            )
            $Raw = $null
            $Rows = $null
            $Raw = Invoke-RestMethod -Uri "$BaseUri\$name" -ErrorAction Stop
            if($Raw -is [system.string]) {
                $Rows = $Raw -split "`n"
            }
            if($Rows -and $Rows.count -gt 1) {
                $Output = @{System = [System.Web.HttpUtility]::UrlDecode($Name)}
                foreach($Row in $Rows) {
                    if($Row -match ":") {
                        $SplitRow = @($Row -split ":")
                        $Key = $SplitRow[0]
                        $Count = $SplitRow.count
                        $Value = $SplitRow[1..$Count] -join ":"
                        if($Value -is [system.string]){$Value = $Value.trim()}
                        if($key -is [system.string]){$key = $key.trim()}
                        $Output.add($Key, $Value)
                    }
                }
                [pscustomobject]$Output
            }
        }

        foreach($Name in $ComputerName) {
            # this isn't a real API, it's duct tape from a blog post - handy, but... we'll do the work.
            $SearchAll = $False
            $Wildcard = $False
            if($Name -match '\*') {
                $SearchAll = $True
                $WildCard = $True
            }
            try {
                ConvertFrom-RackFacts -BaseUri $BaseUri -Name $Name -ErrorAction Stop
            }
            catch {
                if($_.Exception.Message -match '404') {
                    $SearchAll = $True
                }
                else {
                    Write-Error $_
                }
            }

            if($SearchAll) {
                if(-not $Wildcard) {
                    $Name = "*$Name*"
                }
                $d = Invoke-WebRequest $BaseUri
                $MatchingLinks = $d.links.where({$_.href -notmatch "^\?|^/rack" -and $_.href -like $Name})
                foreach($Link in $MatchingLinks) {
                    ConvertFrom-RackFacts -BaseUri $BaseUri -Name $Link.href
                }
            }
        }
    }
}

$Unique = "${Prefix}${MergeProperty}"
$Date = Get-Date

$Nodes = Get-RackFacts -BaseUri $BaseUri -ComputerName *
$Nodes = $Nodes |
        Select-Object -Property $Properties |
        Select-Object -Property $Transforms

$names = $nodes.NameLower
$NameHash = @{}
$Dupes = foreach($Name in $Names) {
    if(-not $NameHash.ContainsKey($Name)){
        $NameHash.Add($Name,$null)
    }
    else {
        $Name
    }
}

$Nodes = Foreach($Node in $Nodes.where({$Dupes -notcontains $_.NameLower})) {
    $Output = Add-PropertyPrefix -Prefix $Prefix -Object $Node
    Add-Member -InputObject $Output -MemberType NoteProperty -Name "${script:CMDBPrefix}${Prefix}UpdateDate" -Value $Date -Force
    if($AllLower) {
        ConvertTo-Lower -InputObject $Output    
    }
    $Output
}

$TotalCount = $Nodes.count
$Count = 0
Foreach($Node in $Nodes) {
    Write-Progress -Activity "Updating Neo4j" -Status  "Adding $($Node.$Unique)" -PercentComplete (($Count / $TotalCount)*100)
    $Count++
    foreach($Domain in $Domains) {
        # Update existing, do not create new nodes, data is garbage
        Set-Neo4jNode -Label $Label -Hash @{$script:ServerUnique = "$($Node.$Unique).$Domain"} -InputObject $Node -NoCreate
    }
}