Developer Guide: Custom Plugins¶
With the Icinga PowerShell Framework you have the possibility to create new check plugins with very small effort. Below you will find a step-by-step tutorial for writing an example one.
File Structure¶
For plugins we will have to distinguish between general components. The plugin file itself with the Cmdlet and the general check/threshold comparison and possible data providers, delivery the content for our modules. If you for example write plugins for your application monitoring and you require different functions to collect these information, the way to go is to separate the collector functions from the plugin itself.
This will result in the following file structure
module
|_ plugin.psd1
|_ plugin.psm1
|_ provider
|_ your_plugin_provider.psm1
This will ensure these functions can be called separately from the plugin itself and make re-using them a lot easier. In addition, it will help other developers to build dependencies based on your module and data collectors to allow an easier re-usage of already existing components.
Additional required files within the provider
folder can be included by using the NestedModules
array within your psd1
file. This will ensure these files are automatically loaded once a new PowerShell session is started.
Creating A New Module¶
The best approach for creating a custom plugin is by creating an independent module which is installed in your PowerShell modules directly. This will ensure you are not overwriting your custom data with possible framework updates.
Developer Tools¶
To get started easier, you can run this command to create the new module:
New-IcingaForWindowsComponent -Name 'plugintutorial' -ComponentType 'plugins';
If you wish to create the module manually, please read on.
Manual Creation¶
In this guide, we will assume the name of the module is icinga-powershell-plugintutorial
.
At first we will have to create a new module. Navigate to the PowerShell modules folder the Framework itself is installed to. In this tutorial we will assume the location is
C:\Program Files\WindowsPowerShell\Modules
Now create a new folder with the name icinga-powershell-plugintutorial
and navigate into it.
As we require a psm1
file which contains our code, we will create a new file with the name icinga-powershell-plugintutorial.psm1
. This will allow the PowerShell autoloader to load the module automatically.
Note: It could be possible, depending on your execution policies, that your module is not loaded properly. If this is the case, you can try to unblock the file by opening a PowerShell and use the Unblock-File
Cmdlet
Unblock-File -Path 'C:\Program Files\WindowsPowerShell\Modules\icinga-powershell-plugintutorial\icinga-powershell-plugintutorial.psm1'
Testing The Module¶
Once the module files are created and unblocked, we can start testing if the autoloader is properly working and our module is detected.
For this open the file icinga-powershell-plugintutorial.psm1
in your preferred editor and add the following code snippet
function Test-MyIcingaPluginTutorialCommand()
{
Write-Host 'Module was loaded';
}
Now open a new PowerShell terminal or write powershell
into an already open PowerShell prompt and execute the command Test-MyIcingaPluginTutorialCommand
.
If everything went properly, you should now read the output Module was loaded
in our prompt. If not, you can try to import the module by using
Import-Module 'C:\Program Files\WindowsPowerShell\Modules\icinga-powershell-plugintutorial\icinga-powershell-plugintutorial.psm1';
inside your console prompt. After that try again to execute the command Test-MyIcingaPluginTutorialCommand
and check if it works this time. If not, you might check the naming of your module to ensure folder name
and .psm1 file name
is identical.
Once this is working, we can remove the function again as we no longer require it.
Create A New Plugin¶
Once everything is working properly we can create our starting function we later use to execute our plugin.
For naming guidelines we will have to begin with the Invoke-IcingaCheck
naming and an identifier of what are going to achieve with our plugin. This is mandatory
to ensure all auto-generation Cmdlets are still working. In our example we will simply name it Tutorial
.
So lets get started with the function
function Invoke-IcingaCheckTutorial()
{
# Our code belongs here
}
Basic Plugin Architecture¶
A basic plugin contains of multiple parts. At first we general arguments
to parse thresholds through. In addition to that we will make use of several functions to create our check and check results. The functions New-IcingaCheck, New-IcingaCheckPackage and New-IcingaCheckResult
will do the work for us.
Writing Our Base-Skeleton¶
For our plugin we will start with param()
to parse arguments to our module, create a check objects and return the result.
At first we will create a variable inside our Start-IcingaAgentServiceTest
function.
function Invoke-IcingaCheckTutorial()
{
# Create our arguments we can use to parse thresholds
# Example: Invoke-IcingaCheckTutorial -Warning 10 -Critical 30
param (
$Warning = $null,
$Critical = $null
);
# Create a new object we can check on. This will include
# comparing values and checking if they are between a
# range is Unknown
$Check = New-IcingaCheck `
-Name 'Tutorial';
# Return our checkresult for the provided check and compile it
# This function will take care to write the plugin output and
# with return we will return the exit code to determine if our
# check is Ok, Warning, Critical or Unknown
return (New-IcingaCheckResult -Check $Check -Compile)
}
To test this module, we can call another PowerShell instance within our current session and execute the code. This will ensure, we are always loading changes we are making:
powershell -C { Use-Icinga; Invoke-IcingaCheckTutorial; }
Our tutorial plugin will now output the current status, the name, performance data and the exit.
[OK] Tutorial:
| 'tutorial'=;;
0
Optional Performance Data¶
To make performance data optional on user input, we can now add another argument to our parameter list and update our check result object to use this argument
function Invoke-IcingaCheckTutorial()
{
# Create our arguments we can use to parse thresholds
# Example: Invoke-IcingaCheckTutorial -Warning 10 -Critical 30
param (
$Warning = $null,
$Critical = $null,
[switch]$NoPerfData = $FALSE
);
# Create a new object we can check on. This will include
# comparing values and checking if they are between a
# range is Unknown
$Check = New-IcingaCheck `
-Name 'Tutorial';
# Return our checkresult for the provided check and compile it
# This function will take care to write the plugin output and
# with return we will return the exit code to determine if our
# check is Ok, Warning, Critical or Unknown
return (New-IcingaCheckResult -Check $Check -NoPerfData $NoPerfData -Compile)
}
Once we call the module with the NoPerfData
argument, performance data is no longer printed
powershell -C { Use-Icinga; Invoke-IcingaCheckTutorial -NoPerfData; }
[OK] Tutorial:
0
Add Value to Check-Object¶
Now as the basic skeleton is ready, we can dive into the actual check object. In our example we will use a random value
, but feel free to add any other related PowerShell value fetched by WMI, APIs or other components here.
function Invoke-IcingaCheckTutorial()
{
# Create our arguments we can use to parse thresholds
# Example: Invoke-IcingaCheckTutorial -Warning 10 -Critical 30
param (
$Warning = $null,
$Critical = $null,
[switch]$NoPerfData = $FALSE
);
# Create a new object we can check on. This will include
# comparing values and checking if they are between a
# range is Unknown
$Check = New-IcingaCheck `
-Name 'Tutorial' `
-Value (
Get-Random -Minimum 10 -Maximum 100
);
# Return our checkresult for the provided check and compile it
# This function will take care to write the plugin output and
# with return we will return the exit code to determine if our
# check is Ok, Warning, Critical or Unknown
return (New-IcingaCheckResult -Check $Check -NoPerfData $NoPerfData -Compile)
}
By doing so, nothing will change from the plugin output in general, besides the performance data in case we wish to output them and the value the object is now holding.
powershell -C { Use-Icinga; Invoke-IcingaCheckTutorial }
[OK] Tutorial: 79
| 'tutorial'=79;;
0
Compare Value with Thresholds¶
Now as we are holding a value inside our check object, we can start to compare it with our Warning
and Critical
thresholds. There are a bunch of functions inside the check object available for this which can be found in the check object documentation.
For most plugins the generic approach will do just fine. This one will ensure we can use the Nagios/Icinga threshold syntax to compare values more dynamically and add ranges support. (See also Icinga Plugins)
The two functions we will use for this are WarnOutOfRange
and CritOutOfRange
.
function Invoke-IcingaCheckTutorial()
{
# Create our arguments we can use to parse thresholds
# Example: Invoke-IcingaCheckTutorial -Warning 10 -Critical 30
param (
$Warning = $null,
$Critical = $null,
[switch]$NoPerfData = $FALSE
);
# Create a new object we can check on. This will include
# comparing values and checking if they are between a
# range is Unknown
$Check = New-IcingaCheck `
-Name 'Tutorial' `
-Value (
Get-Random -Minimum 10 -Maximum 100
);
# Each compare function within our check object will return the
# object itself, allowing us to write a nested call like below
# to compare multiple values at once.
# IMPORTANT: We have to output the last call either to Out-Null
# or store the result inside a variable, as the check
# object is otherwise written into our plugin output
$Check.WarnOutOfRange($Warning).CritOutOfRange($Critical) | Out-Null;
# Return our checkresult for the provided check and compile it
# This function will take care to write the plugin output and
# with return we will return the exit code to determine if our
# check is Ok, Warning, Critical or Unknown
return (New-IcingaCheckResult -Check $Check -NoPerfData $NoPerfData -Compile)
}
NOTE: It is very important to output the function calls either to Out-Null
or assign a variable to store the content, as otherwise we will spam our check object into our plugin output
$dump = $Check.WarnOutOfRange($Warning).CritOutOfRange($Critical);
As we have now added comparing functions to our plugin, we can execute the plugin again and check if everything works as intended
powershell -C { Use-Icinga; Invoke-IcingaCheckTutorial -Warning 20 -Critical 30 }
[CRITICAL] Tutorial: Value "76" is greater than threshold "30"
| 'tutorial'=76;20;30
2
Using Check Packages¶
Now it is time to combine multiple check objects into one check package. Our basic plugin works just fine, but maybe we wish to compare multiple values for multiple checks. To do so, we will create another check object
and one check package object
.
Don’t forget to add the compare functions WarnOutOfRange
and CritOutOfRange
for the new check object
!
Last but not least we will modify our New-IcingaCheckResult
function to use the check package
instead of our old check object
function Invoke-IcingaCheckTutorial()
{
# Create our arguments we can use to parse thresholds
# Example: Invoke-IcingaCheckTutorial -Warning 10 -Critical 30
param (
$Warning = $null,
$Critical = $null,
[switch]$NoPerfData = $FALSE
);
# Create a new object we can check on. This will include
# comparing values and checking if they are between a
# range is Unknown
$Check = New-IcingaCheck `
-Name 'Tutorial' `
-Value (
Get-Random -Minimum 10 -Maximum 100
);
# Add another check objects with a different name for identifying
# which check is holding which value
$Check2 = New-IcingaCheck `
-Name 'Tutorial 2' `
-Value (
Get-Random -Minimum 10 -Maximum 100
);
# Each compare function within our check object will return the
# object itself, allowing us to write a nested call like below
# to compare multiple values at once.
# IMPORTANT: We have to output the last call either to Out-Null
# or store the result inside a variable, as the check
# object is otherwise written into our plugin output
$Check.WarnOutOfRange($Warning).CritOutOfRange($Critical) | Out-Null;
# Don't forget to add our comparison for the second check with
# the identical thresholds. If you want to, you could compare
# them to different arguments
$Check2.WarnOutOfRange($Warning).CritOutOfRange($Critical) | Out-Null;
# Now lets define a check package we can combine our checks into.
# Check packages have names themself and provide a function to
# add checks into. We can either add them directly during creation
# or later
$CheckPackage = New-IcingaCheckPackage `
-Name 'Tutorial Package' `
-Checks @(
$Check,
$Check2
);
# Alternatively we can also call the method AddCheck
# $CheckPackage.AddCheck($Check);
# $CheckPackage.AddCheck($Check2);
# Return our checkresult for the provided check and compile it
# This function will take care to write the plugin output and
# with return we will return the exit code to determine if our
# check is Ok, Warning, Critical or Unknown
return (New-IcingaCheckResult -Check $CheckPackage -NoPerfData $NoPerfData -Compile)
}
If we now call our script plugin again, we will see two output for performance data
powershell -C { Use-Icinga; Invoke-IcingaCheckTutorial -Warning 20 -Critical 30 }
[OK] Check package "Tutorial Package"
| 'tutorial'=63;20;30 'tutorial_2'=37;20;30
0
Package Operators¶
As you see, the plugin output is Ok
while clearly it should throw Critical
. What we are missing is a comparing operator, telling the package how to count each assigned check. We have several operators on our hand:
-OperatorMin <number>
with<number>
amount of checks require to be ok for the package to be ok-OperatorMax <number>
with<number>
amount of checks require to be ok for the package to be ok-OperatorAnd
for all checks requiring to be ok for the package to be ok-OperatorOr
for at least one check requiring to be ok for the package to be ok-OperatorNone
for all checks to benot
ok for the package to be ok
You can only use one operator per check package, a combination is not possible.
On our example we will use the -OperatorAnd
to ensure all checks have to be ok for the package to be ok
function Invoke-IcingaCheckTutorial()
{
# Create our arguments we can use to parse thresholds
# Example: Invoke-IcingaCheckTutorial -Warning 10 -Critical 30
param (
$Warning = $null,
$Critical = $null,
[switch]$NoPerfData = $FALSE
);
# Create a new object we can check on. This will include
# comparing values and checking if they are between a
# range is Unknown
$Check = New-IcingaCheck `
-Name 'Tutorial' `
-Value (
Get-Random -Minimum 10 -Maximum 100
);
# Add another check objects with a different name for identifying
# which check is holding which value
$Check2 = New-IcingaCheck `
-Name 'Tutorial 2' `
-Value (
Get-Random -Minimum 10 -Maximum 100
);
# Each compare function within our check object will return the
# object itself, allowing us to write a nested call like below
# to compare multiple values at once.
# IMPORTANT: We have to output the last call either to Out-Null
# or store the result inside a variable, as the check
# object is otherwise written into our plugin output
$Check.WarnOutOfRange($Warning).CritOutOfRange($Critical) | Out-Null;
# Don't forget to add our comparison for the second check with
# the identical thresholds. If you want to, you could compare
# them to different arguments
$Check2.WarnOutOfRange($Warning).CritOutOfRange($Critical) | Out-Null;
# Now lets define a check package we can combine our checks into.
# Check packages have names themself and provide a function to
# add checks into. We can either add them directly during creation
# or later
$CheckPackage = New-IcingaCheckPackage `
-Name 'Tutorial Package' `
-Checks @(
$Check,
$Check2
) `
-OperatorAnd;
# Alternatively we can also call the method AddCheck
# $CheckPackage.AddCheck($Check);
# $CheckPackage.AddCheck($Check2);
# Return our checkresult for the provided check and compile it
# This function will take care to write the plugin output and
# with return we will return the exit code to determine if our
# check is Ok, Warning, Critical or Unknown
return (New-IcingaCheckResult -Check $CheckPackage -NoPerfData $NoPerfData -Compile)
}
Now lets see how the output changes
powershell -C { Use-Icinga; Invoke-IcingaCheckTutorial -Warning 20 -Critical 30 }
[CRITICAL] Check package "Tutorial Package" - [CRITICAL] Tutorial, Tutorial 2
\_ [CRITICAL] Tutorial: Value "52" is greater than threshold "30"
\_ [CRITICAL] Tutorial 2: Value "60" is greater than threshold "30"
| 'tutorial'=52;20;30 'tutorial_2'=60;20;30
2
As you can see our package is now critical, outputting each check which is not
Ok. In addition the functions will add
[CRITICAL] Tutorial, Tutorial 2
inside the short plugin output to ensure we have a quick overview within Icinga Web 2, telling us which checks are failing.
Increasing Verbosity¶
In case our checks are ok, they are not printed by default to keep the view as little as possible. We can test this by executing the plugin without thresholds
powershell -C { Use-Icinga; Invoke-IcingaCheckTutorial }
[OK] Check package "Tutorial Package"
| 'tutorial'=63;; 'tutorial_2'=51;;
0
In case we want to make it configurable if every single check object
and check package
is printed, we can add a Verbosity
flag. This will also introduce another method for the params
of the module, as we will only allow certain input values for the Verbosity
argument.
In addition, we will parse the new $Verbosity
as argument to our check package
function Invoke-IcingaCheckTutorial()
{
# Create our arguments we can use to parse thresholds
# Example: Invoke-IcingaCheckTutorial -Warning 10 -Critical 30
param (
$Warning = $null,
$Critical = $null,
[switch]$NoPerfData = $FALSE,
# Ensure only 0-2 values are allowed for Verbosity
[ValidateSet(0, 1, 2)]
[int]$Verbosity = 0
);
# Create a new object we can check on. This will include
# comparing values and checking if they are between a
# range is Unknown
$Check = New-IcingaCheck `
-Name 'Tutorial' `
-Value (
Get-Random -Minimum 10 -Maximum 100
);
# Add another check objects with a different name for identifying
# which check is holding which value
$Check2 = New-IcingaCheck `
-Name 'Tutorial 2' `
-Value (
Get-Random -Minimum 10 -Maximum 100
);
# Each compare function within our check object will return the
# object itself, allowing us to write a nested call like below
# to compare multiple values at once.
# IMPORTANT: We have to output the last call either to Out-Null
# or store the result inside a variable, as the check
# object is otherwise written into our plugin output
$Check.WarnOutOfRange($Warning).CritOutOfRange($Critical) | Out-Null;
# Don't forget to add our comparison for the second check with
# the identical thresholds. If you want to, you could compare
# them to different arguments
$Check2.WarnOutOfRange($Warning).CritOutOfRange($Critical) | Out-Null;
# Now lets define a check package we can combine our checks into.
# Check packages have names themself and provide a function to
# add checks into. We can either add them directly during creation
# or later
$CheckPackage = New-IcingaCheckPackage `
-Name 'Tutorial Package' `
-Checks @(
$Check,
$Check2
) `
-OperatorAnd `
-Verbose $Verbosity;
# Alternatively we can also call the method AddCheck
# $CheckPackage.AddCheck($Check);
# $CheckPackage.AddCheck($Check2);
# Return our checkresult for the provided check and compile it
# This function will take care to write the plugin output and
# with return we will return the exit code to determine if our
# check is Ok, Warning, Critical or Unknown
return (New-IcingaCheckResult -Check $CheckPackage -NoPerfData $NoPerfData -Compile)
}
If we now execute the plugin with Verbosity
and the value 2
, every single check will be printed, even when the check itself is Ok
powershell -C { Use-Icinga; Invoke-IcingaCheckTutorial -Verbosity 2 }
[OK] Check package "Tutorial Package" (Match All)
\_ [OK] Tutorial: 70
\_ [OK] Tutorial 2: 36
| 'tutorial'=70;; 'tutorial_2'=36;;
0
The following Verbose
options are available
0
: Default - onlynot
Ok values will be printed1
: Onlynot
Ok values will be printed including packageoperator
config2
: Everything will be printed
More Complex Checks¶
We will not provide an example in this guide, but we would like to add that this is not the final complexity level of plugins. Each check package
for example could contain multiple check packages
with checks
and even more check packages
.
You simply have to ensure you are adding checks
and check packages
correctly into each object and parse your primary check package
to the check result Cmdlet
. The Framework will then deal with the entire operation and calculation itself
Icinga Configuration¶
Now as we are done with writing our plugin, it is time to test it inside Icinga 2. Instead of having to write an Icinga Director
command configuration yourself, we can use an integrated Framework Cmdlet to generate a Basket
file for us which can be imported into the Icinga Director
.
Get-IcingaCheckCommandConfig -CheckName 'Invoke-IcingaCheckTutorial' -OutDirectory 'C:\users\public';
The following commands have been exported:
- 'Invoke-IcingaCheckTutorial'
JSON export created in 'C:\users\public\PowerShell_CheckCommands_03-31-2020-17-34-5367.json'
This is the reason why it is mandatory
to place plugin Cmdlets within the Invoke-IcingaCheck
“namespace”. Calling Get-IcingaCheckCommandConfig
without the CheckName
argument will automatically lookup every command with this naming schema and export all of them inside the Basket
file.
General Guidelines¶
To ensure the later import into the Icinga Director
and usage within Icinga 2 is as easy as possible, it is recommended to write a proper plugin documentation which is understood by the Get-Help
Cmdlet of Windows. By doing so, arguments are described directly inside the Icinga Director
.
Another important note are correct data types
for values. Each data type
is translated properly into Icinga Director
language, resulting in the following behaviour:
[string]
will be translated to text values on check input fields[int]
will be translated to numeric values on check input fields[bool]
will be translated to yes/no values on check input fields[switch]
will be translated to yes/no values on check input fields[array]
will be translated to array values on check input fields
By using the ValidateSet
feature of PowerShell (as we did for our Verbosity
) we will automatically generate a custom variable
as datalist
, only allowing the valid input values in a drop down list