Skip to content

Commit 6dca6c0

Browse files
committed
Adds developer guide for custom API endpoints
1 parent 591555f commit 6dca6c0

File tree

2 files changed

+270
-0
lines changed

2 files changed

+270
-0
lines changed

doc/04-Developer-Guide.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ A detailed overview of functions can be found below
1515
* [New-IcingaCheckResult](developerguide/03-New-IcingaCheckResult.md)
1616
* [Custom Daemons](developerguide/10-Custom-Daemons.md)
1717
* [Custom Plugins](developerguide/11-Custom-Plugins.md)
18+
* [Custom API-Endpoints](developerguide/12-Custom-API-Endpoints.md)
1819
* [Using Console Outputs](developerguide/20-Using-Console-Outputs.md)
1920
* [Using EventLog Output](developerguide/20-Using-EventLog-Outputs.md)
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
# Developer Guide: Custom API-Endpoints
2+
3+
Starting with Icinga [PowerShell Framework v1.1.0](https://icinga.com/docs/windows/latest) plenty of features and functionaliy have been added for shipping data by using a REST-API. This Developer Guide will describe on how to write custom API endpoints by using the [PowerShell Framework v1.1.0](https://icinga.com/docs/windows/latest) and the [Icinga PowerShell REST-Api](https://icinga.com/docs/windows/latest/restapi/doc/01-Introduction/). In this example we will write a custom endpoint to simply provide a file list for a specific folder.
4+
5+
## File Structure
6+
7+
Like plugins, API endpoints can contain plenty of different files to keep the code clean. To ensure each module is identical and easier to maintain for users, we would advise the following file structure:
8+
9+
```text
10+
module
11+
|_ apiendpoint.psd1
12+
|_ apiendpoint.psm1
13+
|_ lib
14+
|_ function1.psm1
15+
|_ function2.psm1
16+
```
17+
18+
This will ensure these functions can be called separately from the endpoint itself and make re-using them a lot easier. In addition, it will help other developers to build dependencies based on your module and allow an easier re-usage of already existing components.
19+
20+
Additional required files within the `lib` 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.
21+
22+
## Creating A New Module
23+
24+
The best approach for creating a custom API endpoint 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 other module updates.
25+
26+
In this guide, we will assume the name of the module is `icinga-powershell-apitutorial`.
27+
28+
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
29+
30+
```powershell
31+
C:\Program Files\WindowsPowerShell\Modules
32+
```
33+
34+
Now create a new folder with the name `icinga-powershell-apitutorial` and navigate into it.
35+
36+
As we require a `psm1` file which contains our code, we will create a new file with the name `icinga-powershell-apitutorial.psm1`. This will allow the PowerShell autoloader to load the module automatically.
37+
38+
**Note:** It could be possible, depending on your [execution policies](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-executionpolicy?view=powershell-6), 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` Cmdelet
39+
40+
```powershell
41+
Unblock-File -Path 'C:\Program Files\WindowsPowerShell\Modules\icinga-powershell-apitutorial\icinga-powershell-apitutorial.psm1'
42+
```
43+
44+
## Testing The Module
45+
46+
Once the module files are created and unblocked, we can start testing if the autoloader is properly working and our module is detected.
47+
48+
For this open the file `icinga-powershell-apitutorial.psm1` in your prefered editor and add the following code snippet
49+
50+
```powershell
51+
function Test-MyIcingaAPITutorialCommand()
52+
{
53+
Write-Host 'Module was loaded';
54+
}
55+
```
56+
57+
Now open a **new** PowerShell terminal or write `powershell` into an already open PowerShell prompt and execute the command `Test-MyIcingaAPITutorialCommand`.
58+
59+
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
60+
61+
```powershell
62+
Import-Module 'C:\Program Files\WindowsPowerShell\Modules\icinga-powershell-apitutorial\icinga-powershell-apitutorial.psm1';
63+
```
64+
65+
inside your console prompt. After that try again to execute the command `Test-MyIcingaAPITutorialCommand` 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.
66+
67+
Once this is working, we can remove the function again as we no longer require it.
68+
69+
## Create A New API-Endpoint
70+
71+
Once everything is working properly we can create our starting function we later use to execute our API endpoint.
72+
73+
At first we create a new folder `lib` inside our module folder and inside the file `Invoke-IcingaAPITutorialRESTCall.psm1`. For naming guidelines we will have to use `Invoke-Icinga{0}RESTCall`. Replace `{0}` with a unique name describing shortly what your module is doing. The user will not require to use this function later and is only required internally and to have a better look on which function is providing REST endpoints.
74+
75+
So lets get started with the function
76+
77+
```powershell
78+
function Invoke-IcingaAPITutorialRESTCall()
79+
{
80+
# Our code belongs here
81+
}
82+
```
83+
84+
### Basic API Architecture
85+
86+
A developer using the REST-Api integration does not have to worry about anything regarding header fetchting, URL encoding or similar. All data is parsed by the [Icinga PowerShell REST-Api](https://icinga.com/docs/windows/latest/restapi/doc/01-Introduction/) and invoked to our function.
87+
88+
Our API endpoint will be called by a namespace, refering to our actual function executing the code.
89+
90+
### Writing Our Base-Skeleton
91+
92+
For our API endpoint we will start with `param()` to parse arguments to our endpoint which is `standardized`, and has to be followed. Otherwise the integration might not work.
93+
94+
```powershell
95+
function Invoke-IcingaAPITutorialRESTCall()
96+
{
97+
# Create our arguments the REST-Api daemon will use to parse the request
98+
param (
99+
[Hashtable]$Request = @{},
100+
[Hashtable]$Connection = @{},
101+
$IcingaGlobals
102+
);
103+
}
104+
```
105+
106+
#### Request Argument
107+
108+
The request argument provides a hashtable with all parsed content of the request to later work with. The following elements are avaialble by default:
109+
110+
##### Method
111+
112+
The HTTP method being used for the request, like `GET`, `POST`, `DELETE` and so on
113+
114+
##### RequestPath
115+
116+
The request path is split into two hastable entries: `FullPath` and `PathArray`. This tells you exactly which URL the user specified and allows you to build proper handling for different entry points of your endpoint.
117+
118+
For the path array, on index 0 you will always find the `version` and on index 1 `your endpoint alias`. Following this, possible additional path extensions in your module will always start on index 2.
119+
120+
##### Header
121+
122+
A hashtable containing all send headers by the client. If you require your client to send additional headers for certain tasks to work, you can check with this if the header is set with the correct value.
123+
124+
```powershell
125+
Name Value
126+
---- -----
127+
Upgrade-Insecure-Requests 1
128+
User-Agent Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
129+
Accept text/html,application/json
130+
Host example.com:5668
131+
Sec-Fetch-Dest document
132+
Accept-Language de,en-US;q=0.9,en;q=0.8
133+
Connection keep-alive
134+
Accept-Encoding gzip, deflate, br
135+
Sec-Fetch-Mode navigate
136+
sec-ch-ua-mobile ?0
137+
X-CustomHeader Custom Content
138+
```
139+
140+
##### RequestArguments
141+
142+
Of course we will also handle possible request arguments. This could either be used for filtering or to modify returned content depending on the input. An example could look like this:
143+
144+
```text
145+
https://example.com:5668/v1/apitutorial?include=*psm1&exclude=*api*
146+
```
147+
148+
```powershell
149+
Name Value
150+
---- -----
151+
include {*psm1}
152+
exclude {*api*}
153+
```
154+
155+
##### Body
156+
157+
The content send by the client in case a method is used to send data.
158+
159+
**Note**: The body argument is only available in case data is send. If the client is using `POST` and sending no data, the argument is not present.
160+
161+
##### FullRequest
162+
163+
This argument contains the full request string for possible troubleshooting and debugging.
164+
165+
```text
166+
/v1/apitutorial?include=*psm1&exclude=*api*
167+
```
168+
169+
##### ContentLength
170+
171+
This only applies to any request which can send data as body and tells you how many data was send. This part is moved from the header to this location for easier accessing.
172+
173+
#### Connection Argument
174+
175+
This argument is containing the connection details of the client including the TCP stream object. You only require this for sending data back to the client or for troubleshooting. In general you only have to parse this object to other functions without modifying it.
176+
177+
#### IcingaGlobals Argument
178+
179+
This argument contains all global data and content of the REST-Api background dameon. This will then come in handy to share data between API endpoints and to access some global configuration data.
180+
181+
### Sending Data to the Client
182+
183+
Now we are basically ready to process data. To do so, we will fetch the current folder content of our PowerShell module with `Get-ChildItem` and send this content to our client. For sending data to the client, we can use `Send-IcingaTCPClientMessage`. This Cmdlet will use a `Message` as `New-IcingaTCPClientRESTMessage` object which itself cotains the `HTTPResponse` and our `ContentBody`. In addition to `Send-IcingaTCPClientMessage` we also have to specify the `Stream` to write to. The stream object is part of our `Connection` argument.
184+
185+
All content will be send as JSON encoded, so please ensure you are using a datatype which is convertable by `ConvertTo-Json`.
186+
187+
```powershell
188+
function Invoke-IcingaAPITutorialRESTCall()
189+
{
190+
# Create our arguments the REST-Api daemon will use to parse the request
191+
param (
192+
[Hashtable]$Request = @{},
193+
[Hashtable]$Connection = @{},
194+
$IcingaGlobals
195+
);
196+
197+
# Fetch all file names within our module directory. We filter this to ensure we
198+
# do not have to handle all PSObjects, we our client message functionality will
199+
# try to resolve them. This could end up in an almost infinite loop
200+
$Content = Get-ChildItem -Path 'C:\Program Files\WindowsPowerShell\Modules\' -Recurse | Select-Object 'Name', 'FullName';
201+
202+
# Send the response to the client as 200 "Ok" with out Directory body
203+
Send-IcingaTCPClientMessage -Message (
204+
New-IcingaTCPClientRESTMessage `
205+
-HTTPResponse ($IcingaHTTPEnums.HTTPResponseType.Ok) `
206+
-ContentBody $Content
207+
) -Stream $Connection.Stream;
208+
}
209+
```
210+
211+
### Registering API-Endpoints
212+
213+
Now as we have written a basic function to fetch folder content and to send it back to our client, we will have to `register` our Cmdlet to the endpoint. For this we will open our `icinga-powershell-apitutorial.psm1` and add a `namespace` function which has to follow this naming guideline: `Register-IcingaRESTAPIEndpoint{0}`
214+
215+
Replace `{0}` with the name you have choosen for your `Invoke-Icinga{0}RESTCall`. Once the REST-Api Daemon is loaded, all functions within this namespace are executed. The function has to return a hashtable with an `Alias` refering to the URL part the user has to enter and a `Command` being executed for this alias.
216+
217+
```powershell
218+
function Register-IcingaRESTAPIEndpointAPITutorial()
219+
{
220+
return @{
221+
'Alias' = 'apitutorial';
222+
'Command' = 'Invoke-IcingaAPITutorialRESTCall';
223+
};
224+
}
225+
```
226+
227+
If our module is providing different endpoints, you will have to create multiple register functions. To keep the API how ever clean and prevent conflicting, we advice you to provide only `one` endpoint and handle all other tasks within this endpoint.
228+
229+
As everything is now ready, we can restart our Icinga PowerShell Framework service by using
230+
231+
```powershell
232+
Restart-IcingaService 'icingapowershell';
233+
```
234+
235+
and access our API endpoint by browsing to our API location (in our example we assume you use `5668` as default port):
236+
237+
```text
238+
https://example.com:5668/v1/apitutorial
239+
```
240+
241+
```json
242+
[
243+
{
244+
"Name": "icinga-powershell-apitutorial",
245+
"FullName": "C:\\Program Files\\WindowsPowerShell\\Modules\\icinga-powershell-apitutorial"
246+
},
247+
{
248+
"Name": "icinga-powershell-framework",
249+
"FullName": "C:\\Program Files\\WindowsPowerShell\\Modules\\icinga-powershell-framework"
250+
},
251+
{
252+
"Name": "icinga-powershell-inventory",
253+
"FullName": "C:\\Program Files\\WindowsPowerShell\\Modules\\icinga-powershell-inventory"
254+
},
255+
{
256+
"Name": "icinga-powershell-plugins",
257+
"FullName": "C:\\Program Files\\WindowsPowerShell\\Modules\\icinga-powershell-plugins"
258+
},
259+
{
260+
"Name": "icinga-powershell-restapi",
261+
"FullName": "C:\\Program Files\\WindowsPowerShell\\Modules\\icinga-powershell-restapi"
262+
},
263+
...
264+
]
265+
```
266+
267+
### Conclusion
268+
269+
This is a basic tutorial on how to write custom API-Endpoints and make them available in your environment. Of course you can now start to filter requests depending on the URL the user added, used headers or other input like the body for example. All data send by the client is accessable to developers for writing their own extensions and modules.

0 commit comments

Comments
 (0)