-
-
Notifications
You must be signed in to change notification settings - Fork 38
Infrastructure As Code
- Core Concepts
- Tree Creation
- Infrastructure As Code
- Migration
- Tree Manipulation
- Extensibility
TL;DR:
# Create a new group, device and some sensors if they don't already exist
$tree = ProbeNode "Local Probe" {
GroupNode Servers {
WhenNewNode @{}
DeviceNode exch-1 {
WhenNewNode @{}
SensorNode *exchange* {
WhenNewNode @{
rt = "wmiservice"
Target = "*exchange*"
restart = 1
}
}
}
}
}
$tree | Get-PrtgTreePlan | Apply-PrtgTreePlan
Programming libraries provide users with the tools they need in order to accomplish a given task. If you want to create a new device in PRTG - but only if it doesn't already exist - you can easily do so: simply combine an if
statement, the Get-Device
command and the Add-Device
command and you've got yourself some control flow!
As you start to add more requirements however ("and my device needs to have these tags, with this notification trigger underneath it, and these sensors") such imperative programming starts to get a bit more complicated, not to mention tedious. All of the tools you need to achieve your task are right in front of you, but adding all the required logic and error handling as your program grows in scope can add up to a lot of work.
To combat this complexity, infrastructure as code tools such as Terraform and Desired State Configuration have evolved, allowing you to describe the desired state you would like to achieve, without having to worry about how to accomplish that task, or even whether it has already been done.
In this article, we will see how PrtgAPI's Tree API can be leveraged for achieving true PowerShell based infrastructure as code, enabling you to
- Generate PrtgAPI infrastructure as code configuration files from existing PRTG objects
- Manage your entire PRTG configuration from a single configuration file
- Migrate objects between PRTG servers
- and more
In order to understand how PrtgAPI's infrastructure as code model works, it is important to understand a bit about code blocks
In PowerShell, a pair of curly brackets is used to denote a block of code
function foo
{
if ($true)
{
# do something
}
}
In certain contexts however, this block of code can be interpreted as being a ScriptBlock
- a definition of some code that you would to execute on demand at some later point in time
# The Where-Object cmdlet invokes the ScriptBlock '$_.Name -eq "Ping"' for every returned sensor,
# and returns only those sensors where the predicate returned True
Get-Sensor | where { $_.Name -eq "Ping" }
Typically these script blocks are used for describing relatively short snippets of code that should be executed by cmdlets such as Where-Object
or Invoke-Command
. By designing your code around script blocks however, it is possible to blur the lines between what is a ScriptBlock
and what isn't, enabling a style of declarative programming, such as is seen in PowerShell libraries such as Pester and PrtgXml
# Create a tree consisting of a the "Local Probe" probe, the "dc-1" device
# beneath it, and all of dc-1's child sensors
ProbeNode "Local Probe" {
DeviceNode "dc-1" {
SensorNode *
}
}
In PrtgAPI's code model, each node cmdlet is passed:
- one or more parameters that may identify the object to retrieve from PRTG, as well as
- an optional
ScriptBlock
, containing the children that should be defined under the parent node
When this ScriptBlock
is invoked, every child node contained within it is also invoked, resulting in their child nodes being invoked, and so on. Utilizing this technique, it is possible for us to describe and apply our desired PRTG state all from a single, regular PowerShell file.
PrtgAPI Tree Nodes can be retrieved via retrieved via New-*Node
cmdlets, such as New-SensorNode
, New-DeviceNode
and New-ProbeNode
. All parameters that are present on PrtgAPI's normal value cmdlets (such as Get-Sensor
, Get-Trigger
) can also be used on their corresponding node cmdlets (such as New-SensorNode
, New-TriggerNode
)
# Get all WMI sensors in position 1 or 2 under their parent device
Get-Sensor -Position 1,2 -Tags wmi*
# Model any WMI sensors in position 1 or 2 under their parent device
New-SensorNode -Position 1,2 -Tags wmi*
# Model any notification triggers whose OnNotificationAction name contains "pagerduty"
New-TriggerNode *pagerduty*
When constructing tree nodes via value-cmdlet-parameters like this, it is important to note that, since PrtgAPI's goal is to model the state of your PRTG server for infrastructure as code purposes, PrtgAPI will actually defer making API requests to PRTG until it actually needs to, in order to figure out which specific objects should actually be retrieved.
# Create a node *representing* the ping sensor of *any* device
$sensorNode = New-SensorNode ping
# Retrieve the Device with ID 1001, retrieve the child ping sensor of device ID 1001 and then
# wrap the Device and child SensorNode up in a DeviceNode
$deviceNode = New-DeviceNode -Id 1001 { $sensorNode }
For more information on how deferred nodes work, please see Deferred Tree Values. PrtgAPI will create a deferred value node any time ambiguous property parameters are specified, thereby allowing PrtgAPI infrastructure as code to be portable between differing PRTG servers where equivalent objects may exist with different object IDs. PrtgAPI will not create deferred tree values if any of the following parameters are specified
-Id
-ParentId
-Global
# Retrieve the group(s) called "Servers" - later - from *some* to-be-determined parent
$node = GroupNode Servers
# Retrieve _all_ the group(s) called "Servers" - now - that are present *anywhere* in PRTG
$node = GroupNode Servers -Global
When you invoke a New-*Node
cmdlet, you specify parameters that help signify which object within PRTG should eventually be retrieved. But what if that object doesn't actually exist yet?
Normally, creating new objects via PrtgAPI is a multi-step process that involves specifying parameters to cmdlets, modifying the creation parameters that are returned, and then finally applying these objects to the server
# Create a new ping sensor
Get-Device -Id 1001 | New-SensorParameters -rt ping | Add-Sensor
# Create some new WMI Service sensors that automatically restart if they are stopped
$params = Get-Device -Id 1001 | New-SensorParameters -rt wmiservice -Target *exchange*
$params.restart = 1
$params | Add-Sensor
# Create a new device
Get-Group -Id 1001 | Add-Device dc-1 dc-1.contoso.local
# Create a new notification trigger
Get-Probe | New-Trigger -Type State -OnNotificationAction *ticket* -Latency 40
PrtgAPI lets you do all of above via the use of the virtual WhenNewNode
function
SensorNode *exchange* {
WhenNewNode @{
rt = "wmiservice"
target = "*exchange*"
restart = 1
}
}
WhenNewNode
accepts a Hashtable
of parameters that should be used for creating the new object. PrtgAPI then parses these parameters and figures out what to do with them! So the invocation
WhenNewNode @{
rt = "wmiservice"
target = "*exchange*"
restart = 1
}
would be considered equivalent to normally doing
$params = New-SensorParameters -rt wmiservice -target *exchange*
$params.restart = 1
When PrtgAPI performs this magic, it literally is invoking New-SensorParameters
internally with the -RawType
and -Target
parameters, and then setting the restart
property on the resulting response. The following table lists the inner cmdlet calls WhenNewNode
gets translated into depending on the parent node it is used within
ParentNode | WhenNewNode | Inner Call |
---|---|---|
SensorNode |
WhenNewNode @{rt="ping"; timeout=20} |
$params = New-SensorParameters -rt "ping" $params.timeout=20 $params | Add-Sensor
|
DeviceNode |
WhenNewNode @{IPVersion="IPv4"} |
$params = New-DeviceParameters $params.IPVersion="IPv4" $params | Add-Device
|
GroupNode |
WhenNewNode @{} |
$params = New-GroupParameters $params | Add-Group
|
TriggerNode |
WhenNewNode @{Type="State"; Latency=40} |
New-Trigger -Type State -Latency 40 |
WhenNewNode
allows the use of both parameters that would normally be passed to the cmdlet or the resulting parameters object. In the case of New-Trigger
, all of the properties on TriggerParameters
objects are parameters to the New-Trigger
cmdlet, so either way you can fully configure the trigger you'd like to create via the use of the WhenNewNode
virtual function.