Skip to content

Commit 2747327

Browse files
Add GLPI Alert Transport (librenms#17127)
* Import Glpi.php from GitLab * Update Glpi.php for styleci compatibility * Second style compliance commit * Third StyleCI compliance commit * Apply fixes from StyleCI * Fix: close ticket when state is 0 * Remove whitespace * Remove http requests and use all assets (#4) * Add GLPI Transport documentation --------- Co-authored-by: StyleCI Bot <[email protected]>
1 parent 91b1734 commit 2747327

File tree

2 files changed

+252
-0
lines changed

2 files changed

+252
-0
lines changed

LibreNMS/Alert/Transport/Glpi.php

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
<?php
2+
3+
/* Copyright (C) 2025 Raphaël Aubry <[email protected]>
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <https://www.gnu.org/licenses/>. */
16+
17+
/**
18+
* IRC Transport
19+
*
20+
* @author Raphaël Aubry <[email protected]>
21+
* @copyright 2025 ASPerience
22+
* @license GPL
23+
*/
24+
25+
namespace LibreNMS\Alert\Transport;
26+
27+
use LibreNMS\Alert\Transport;
28+
use LibreNMS\Exceptions\AlertTransportDeliveryException;
29+
use LibreNMS\Util\Http;
30+
31+
class Glpi extends Transport
32+
{
33+
protected string $name = 'GLPI';
34+
35+
public function deliverAlert(array $alert_data): bool
36+
{
37+
// Connect to the API with app/user tokens
38+
$headers = [
39+
'Content-Type' => 'application/json',
40+
'App-Token' => $this->config['app-token'],
41+
];
42+
43+
$data = [
44+
'user_token' => $this->config['user-token'],
45+
'get_full_session' => true,
46+
];
47+
48+
$res = Http::client()
49+
->withHeaders($headers)
50+
->get($this->config['api-url'] . '/initSession', $data);
51+
52+
if (! $res->successful()) {
53+
throw new AlertTransportDeliveryException($alert_data, $res->status(), $res->body(),
54+
$alert_data['msg'], $data);
55+
}
56+
57+
$headers['Session-Token'] = $res->json()['session_token'];
58+
$userID = $res->json()['session']['glpiID'];
59+
$profileID = $res->json()['session']['glpiactiveprofile']['id'];
60+
61+
// Change the active profile to super-admin (always 4 in GLPI)
62+
if ($profileID != 4) {
63+
$data = [
64+
'profiles_id' => 4,
65+
];
66+
67+
$res = Http::client()
68+
->withHeaders($headers)
69+
->post($this->config['api-url'] . '/changeActiveProfile/', $data);
70+
}
71+
72+
// Retrieve the ticket for the alert (by title)
73+
$ticketURL = $this->config['api-url'] . '/Ticket';
74+
$searchURL = $this->config['api-url'] .
75+
'/search/Ticket?' .
76+
'forcedisplay[0]=2&' .
77+
'forcedisplay[1]=12&' .
78+
'criteria[0][field]=1&' .
79+
'criteria[0][searchtype]=contains&' .
80+
'criteria[0][value]=^[LibreNMS: ' . $alert_data['sysName'] . '] ' . $alert_data['name'] . '$&' .
81+
'criteria[1][link]=AND&' .
82+
'criteria[1][field]=12&' .
83+
'criteria[1][searchtype]=equals&' .
84+
'criteria[1][value]=notclosed';
85+
86+
$res = Http::client()
87+
->withHeaders($headers)
88+
->get($searchURL);
89+
90+
if (! array_key_exists('data', $res->json())) {
91+
// No ticket for the alert found, create a new one
92+
93+
// Retrieve the device in GLPI
94+
$deviceSearchURL = $this->config['api-url'] .
95+
'/search/AllAssets?' .
96+
'forcedisplay[0]=2&' .
97+
'forcedisplay[1]=80&' .
98+
'criteria[0][field]=1&' .
99+
'criteria[0][searchtype]=contains&' .
100+
'criteria[0][value]=^' . $alert_data['sysName'] . '$';
101+
102+
$res = Http::client()
103+
->withHeaders($headers)
104+
->get($deviceSearchURL);
105+
106+
$deviceID = $res->json()['data'][0]['2'] ?? null;
107+
$itemtype = $res->json()['data'][0]['itemtype'] ?? null;
108+
109+
// Retrieve the entity in GLPI
110+
$entityName = $res->json()['data'][0]['80'] ?? null;
111+
$entityID = null;
112+
if ($entityName != null) {
113+
$entitySearchURL = $this->config['api-url'] .
114+
'/Entity?searchText[completename]=^' . $entityName . '$';
115+
116+
$res = Http::client()
117+
->withHeaders($headers)
118+
->get($entitySearchURL);
119+
120+
$entityID = $res->json()[0]['id'] ?? null;
121+
}
122+
123+
// Create the ticket
124+
$data = [
125+
'input' => [
126+
'name' => '[LibreNMS: ' . $alert_data['sysName'] . '] ' . $alert_data['name'],
127+
'content' => $alert_data['msg'],
128+
'_users_id_requester' => $userID,
129+
],
130+
];
131+
132+
if ($entityID != null) {
133+
$data['input']['entities_id'] = $entityID;
134+
}
135+
136+
$res = Http::client()
137+
->withHeaders($headers)
138+
->post($ticketURL, $data);
139+
140+
// Associate GLPI device to the ticket
141+
if ($res->successful() && $deviceID != null) {
142+
$ticketID = $res->json()['id'];
143+
144+
$data = [
145+
'input' => [
146+
'items_id' => $deviceID,
147+
'itemtype' => $itemtype,
148+
'tickets_id' => $ticketID,
149+
],
150+
];
151+
152+
$res = Http::client()
153+
->withHeaders($headers)
154+
->post($this->config['api-url'] . '/Item_Ticket', $data);
155+
}
156+
} else {
157+
$ticketID = $res->json()['data'][0]['2'];
158+
$ticketStatus = $res->json()['data'][0]['12'];
159+
160+
// Add followup to ticket
161+
$data = [
162+
'input' => [
163+
'content' => $alert_data['msg'],
164+
'itemtype' => 'Ticket',
165+
'items_id' => $ticketID,
166+
],
167+
];
168+
169+
$followupURL = $this->config['api-url'] . '/ITILFollowup';
170+
171+
$res = Http::client()
172+
->withHeaders($headers)
173+
->post($followupURL, $data);
174+
175+
if ($ticketStatus == 5) {
176+
// Reopen the ticket if it was resolved or close it if the state is 0
177+
$data = [
178+
'input' => [
179+
'status' => 2,
180+
],
181+
];
182+
183+
if ($alert_data['state'] == 0) {
184+
$data['input']['status'] = 6;
185+
}
186+
187+
$res = Http::client()
188+
->withHeaders($headers)
189+
->patch($this->config['api-url'] . '/Ticket/' . $ticketID, $data);
190+
}
191+
}
192+
193+
if ($res->successful()) {
194+
return true;
195+
}
196+
197+
throw new AlertTransportDeliveryException($alert_data, $res->status(), $res->body(),
198+
$alert_data['msg'], $data);
199+
}
200+
201+
public static function configTemplate(): array
202+
{
203+
return [
204+
'config' => [
205+
[
206+
'title' => 'GLPI API URL',
207+
'name' => 'api-url',
208+
'descr' => 'API URL of GLPI (typically ending in apirest.php)',
209+
'type' => 'text',
210+
],
211+
[
212+
'title' => 'User Token',
213+
'name' => 'user-token',
214+
'descr' => 'GLPI user token for API access (to generate: User preferences > API)',
215+
'type' => 'text',
216+
],
217+
[
218+
'title' => 'App Token',
219+
'name' => 'app-token',
220+
'descr' => 'App token for API access (to generate: Configuration > General > API)',
221+
'type' => 'text',
222+
],
223+
],
224+
'validation' => [
225+
'api-url' => 'required|url',
226+
'user-token' => 'required|string',
227+
'app-token' => 'required|string',
228+
],
229+
];
230+
}
231+
}

doc/Alerting/Transports/GLPI.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
## GLPI
2+
3+
The GLPI transport creates a ticket in GLPI whenever an alert is raised.
4+
5+
- For each alert type on a device, a ticket is created.
6+
- If multiple alerts of the same type are raised, follow-ups are added to the existing ticket.
7+
- If the existing ticket is closed, it will create another ticket.
8+
9+
The user identified by the user token will be set as the creator and the requester of the ticket. If a device with the same name exists in GLPI, it will be linked to the ticket.
10+
11+
To set it up:
12+
- **User token**: Go to User preferences > API in GLPI.
13+
- **App token**: Go to Configuration > General > API in GLPI.
14+
15+
**Example:**
16+
17+
| Config | Example |
18+
| ------ | ------- |
19+
| GLPI API URL | <http://localhost/glpi/apirest.php> |
20+
| User Token | A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0 |
21+
| App Token | Z9y8X7w6V5u4T3s2R1q0P9o8N7m6L5k4J3i2H1g |

0 commit comments

Comments
 (0)