mirror of
https://github.com/librenms/librenms.git
synced 2024-09-21 10:28:13 +00:00
Better handling of some alerting errors (#13446)
* Better handling of some alerting errors * Better error output * Consolidate simple template parsing * Fixes reported by phpstan (one was a bug, yay!) * add back forgotten trim * don't remove the template if there is no match * Match previous behavior, which was inconsistent. * use anonymous class for tests instead * Oopsie, Stringable is PHP8+ * fix style
This commit is contained in:
parent
99d2462b80
commit
2c77edf4d2
@ -93,19 +93,19 @@ class AlertUtil
|
||||
$uids = [];
|
||||
foreach ($results as $result) {
|
||||
$tmp = null;
|
||||
if (is_numeric($result['bill_id'])) {
|
||||
if (isset($result['bill_id']) && is_numeric($result['bill_id'])) {
|
||||
$tmpa = dbFetchRows('SELECT user_id FROM bill_perms WHERE bill_id = ?', [$result['bill_id']]);
|
||||
foreach ($tmpa as $tmp) {
|
||||
$uids[$tmp['user_id']] = $tmp['user_id'];
|
||||
}
|
||||
}
|
||||
if (is_numeric($result['port_id'])) {
|
||||
if (isset($result['port_id']) && is_numeric($result['port_id'])) {
|
||||
$tmpa = dbFetchRows('SELECT user_id FROM ports_perms WHERE port_id = ?', [$result['port_id']]);
|
||||
foreach ($tmpa as $tmp) {
|
||||
$uids[$tmp['user_id']] = $tmp['user_id'];
|
||||
}
|
||||
}
|
||||
if (is_numeric($result['device_id'])) {
|
||||
if (isset($result['device_id']) && is_numeric($result['device_id'])) {
|
||||
if (Config::get('alert.syscontact') == true) {
|
||||
if (dbFetchCell("SELECT attrib_value FROM devices_attribs WHERE attrib_type = 'override_sysContact_bool' AND device_id = ?", [$result['device_id']])) {
|
||||
$tmpa = dbFetchCell("SELECT attrib_value FROM devices_attribs WHERE attrib_type = 'override_sysContact_string' AND device_id = ?", [$result['device_id']]);
|
||||
|
@ -65,12 +65,12 @@ abstract class Transport implements TransportInterface
|
||||
* @param string $input
|
||||
* @return array
|
||||
*/
|
||||
protected function parseUserOptions($input)
|
||||
protected function parseUserOptions(string $input): array
|
||||
{
|
||||
$options = [];
|
||||
foreach (explode(PHP_EOL, $input) as $option) {
|
||||
foreach (preg_split('/\\r\\n|\\r|\\n/', $input, -1, PREG_SPLIT_NO_EMPTY) as $option) {
|
||||
if (Str::contains($option, '=')) {
|
||||
[$k,$v] = explode('=', $option, 2);
|
||||
[$k, $v] = explode('=', $option, 2);
|
||||
$options[$k] = trim($v);
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
namespace LibreNMS\Alert\Transport;
|
||||
|
||||
use App\View\SimpleTemplate;
|
||||
use LibreNMS\Alert\Transport;
|
||||
use LibreNMS\Util\Proxy;
|
||||
|
||||
@ -46,31 +47,14 @@ class Api extends Transport
|
||||
private function contactAPI($obj, $api, $options, $method, $auth, $headers, $body)
|
||||
{
|
||||
$request_opts = [];
|
||||
$request_heads = [];
|
||||
$query = [];
|
||||
|
||||
$method = strtolower($method);
|
||||
$host = explode('?', $api, 2)[0]; //we don't use the parameter part, cause we build it out of options.
|
||||
|
||||
//get each line of key-values and process the variables for Headers;
|
||||
foreach (preg_split('/\\r\\n|\\r|\\n/', $headers, -1, PREG_SPLIT_NO_EMPTY) as $current_line) {
|
||||
[$u_key, $u_val] = explode('=', $current_line, 2);
|
||||
foreach ($obj as $p_key => $p_val) {
|
||||
$u_val = str_replace('{{ $' . $p_key . ' }}', $p_val, $u_val);
|
||||
}
|
||||
//store the parameter in the array for HTTP headers
|
||||
$request_heads[$u_key] = $u_val;
|
||||
}
|
||||
$request_heads = $this->parseUserOptions(SimpleTemplate::parse($headers, $obj));
|
||||
//get each line of key-values and process the variables for Options;
|
||||
foreach (preg_split('/\\r\\n|\\r|\\n/', $options, -1, PREG_SPLIT_NO_EMPTY) as $current_line) {
|
||||
[$u_key, $u_val] = explode('=', $current_line, 2);
|
||||
// Replace the values
|
||||
foreach ($obj as $p_key => $p_val) {
|
||||
$u_val = str_replace('{{ $' . $p_key . ' }}', $p_val, $u_val);
|
||||
}
|
||||
//store the parameter in the array for HTTP query
|
||||
$query[$u_key] = $u_val;
|
||||
}
|
||||
$query = $this->parseUserOptions(SimpleTemplate::parse($options, $obj));
|
||||
|
||||
$client = new \GuzzleHttp\Client();
|
||||
$request_opts['proxy'] = Proxy::forGuzzle();
|
||||
@ -89,10 +73,7 @@ class Api extends Transport
|
||||
$res = $client->request('PUT', $host, $request_opts);
|
||||
} else { //Method POST
|
||||
$request_opts['query'] = $query;
|
||||
foreach ($obj as $metric => $value) {
|
||||
$body = str_replace('{{ $' . $metric . ' }}', $value, $body);
|
||||
}
|
||||
$request_opts['body'] = $body;
|
||||
$request_opts['body'] = SimpleTemplate::parse($body, $obj);
|
||||
$res = $client->request('POST', $host, $request_opts);
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@
|
||||
|
||||
namespace LibreNMS\Alert\Transport;
|
||||
|
||||
use App\View\SimpleTemplate;
|
||||
use LibreNMS\Alert\Transport;
|
||||
use LibreNMS\Util\Proxy;
|
||||
|
||||
@ -51,9 +52,7 @@ class Matrix extends Transport
|
||||
$request_heads['Content-Type'] = 'application/json';
|
||||
$request_heads['Accept'] = 'application/json';
|
||||
|
||||
foreach ($obj as $p_key => $p_val) {
|
||||
$message = str_replace('{{ $' . $p_key . ' }}', $p_val, $message);
|
||||
}
|
||||
$message = SimpleTemplate::parse($message, $obj);
|
||||
|
||||
$body = ['body'=>$message, 'msgtype'=>'m.text'];
|
||||
|
||||
|
@ -28,6 +28,8 @@ use LibreNMS\Util\Proxy;
|
||||
|
||||
class Rocket extends Transport
|
||||
{
|
||||
protected $name = 'Rocket Chat';
|
||||
|
||||
public function deliverAlert($obj, $opts)
|
||||
{
|
||||
$rocket_opts = $this->parseUserOptions($this->config['rocket-options']);
|
||||
@ -51,10 +53,10 @@ class Rocket extends Transport
|
||||
'text' => $rocket_msg,
|
||||
],
|
||||
],
|
||||
'channel' => $api['channel'],
|
||||
'username' => $api['username'],
|
||||
'icon_url' => $api['icon_url'],
|
||||
'icon_emoji' => $api['icon_emoji'],
|
||||
'channel' => $api['channel'] ?? null,
|
||||
'username' => $api['username'] ?? null,
|
||||
'icon_url' => $api['icon_url'] ?? null,
|
||||
'icon_emoji' => $api['icon_emoji'] ?? null,
|
||||
];
|
||||
$alert_message = json_encode($data);
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
namespace LibreNMS\Device;
|
||||
|
||||
use App\View\SimpleTemplate;
|
||||
use Cache;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
@ -166,35 +167,29 @@ class YamlDiscovery
|
||||
$value = static::getValueFromData($name, $index, $def, $pre_cache);
|
||||
|
||||
if (is_null($value)) {
|
||||
// built in replacements
|
||||
$search = [
|
||||
'{{ $index }}',
|
||||
'{{ $count }}',
|
||||
// basic replacements
|
||||
$variables = [
|
||||
'index' => $index,
|
||||
'count' => $count,
|
||||
];
|
||||
$replace = [
|
||||
$index,
|
||||
$count,
|
||||
];
|
||||
|
||||
// prepare the $subindexX match variable replacement
|
||||
foreach (explode('.', $index) as $pos => $subindex) {
|
||||
$search[] = '{{ $subindex' . $pos . ' }}';
|
||||
$replace[] = $subindex;
|
||||
$variables['subindex' . $pos] = $subindex;
|
||||
}
|
||||
|
||||
$value = str_replace($search, $replace, $def[$name] ?? '');
|
||||
$value = (string) (new SimpleTemplate($def[$name] ?? '', $variables))->keepEmptyTemplates();
|
||||
|
||||
// search discovery data for values
|
||||
$value = preg_replace_callback('/{{ \$?([a-zA-Z0-9\-.:]+) }}/', function ($matches) use ($index, $def, $pre_cache) {
|
||||
$replace = static::getValueFromData($matches[1], $index, $def, $pre_cache, null);
|
||||
$template = new SimpleTemplate($value);
|
||||
$template->replaceWith(function ($matches) use ($index, $def, $pre_cache) {
|
||||
$replace = static::getValueFromData($matches[1], $index, $def, $pre_cache);
|
||||
if (is_null($replace)) {
|
||||
d_echo('Warning: No variable available to replace ' . $matches[1] . ".\n");
|
||||
\Log::warning('YamlDiscovery: No variable available to replace ' . $matches[1]);
|
||||
|
||||
return ''; // remove the unavailable variable
|
||||
}
|
||||
|
||||
return $replace;
|
||||
}, $value);
|
||||
});
|
||||
$value = (string) $template;
|
||||
}
|
||||
|
||||
return $value;
|
||||
|
@ -27,6 +27,7 @@ namespace LibreNMS\OS\Traits;
|
||||
|
||||
use App\Models\Device;
|
||||
use App\Models\Location;
|
||||
use App\View\SimpleTemplate;
|
||||
use Illuminate\Support\Arr;
|
||||
use LibreNMS\Util\StringHelpers;
|
||||
use Log;
|
||||
@ -75,7 +76,7 @@ trait YamlOSDiscovery
|
||||
}
|
||||
|
||||
$device->$field = isset($os_yaml["{$field}_template"])
|
||||
? $this->parseTemplate($os_yaml["{$field}_template"], $data)
|
||||
? trim(SimpleTemplate::parse($os_yaml["{$field}_template"], $data))
|
||||
: $value;
|
||||
}
|
||||
}
|
||||
@ -131,13 +132,6 @@ trait YamlOSDiscovery
|
||||
}
|
||||
}
|
||||
|
||||
private function parseTemplate($template, $data)
|
||||
{
|
||||
return trim(preg_replace_callback('/{{ ([^ ]+) }}/', function ($matches) use ($data) {
|
||||
return $data[$matches[1]] ?? '';
|
||||
}, $template));
|
||||
}
|
||||
|
||||
private function translateSysObjectID($mib, $regex)
|
||||
{
|
||||
$device = $this->getDevice();
|
||||
|
@ -146,4 +146,15 @@ class StringHelpers
|
||||
|
||||
return $namespace . $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if variable can be cast to a string
|
||||
*
|
||||
* @param mixed $var
|
||||
* @return bool
|
||||
*/
|
||||
public static function isStringable($var): bool
|
||||
{
|
||||
return $var === null || is_scalar($var) || (is_object($var) && method_exists($var, '__toString'));
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,8 @@ class AlertTransportController extends Controller
|
||||
return response()->json(['status' => 'ok']);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$result = $e->getMessage();
|
||||
\Log::error($e);
|
||||
$result = basename($e->getFile(), '.php') . ':' . $e->getLine() . ' ' . $e->getMessage();
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
|
113
app/View/SimpleTemplate.php
Normal file
113
app/View/SimpleTemplate.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/*
|
||||
* SimpleTemplate.php
|
||||
*
|
||||
* Simple variable substitution template
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2021 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace App\View;
|
||||
|
||||
use LibreNMS\Util\StringHelpers;
|
||||
|
||||
class SimpleTemplate
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $template;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $variables;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $regex = '/{{ \$?([a-zA-Z0-9\-_.:]+) }}/';
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
private $callback;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $keepEmpty = false;
|
||||
|
||||
public function __construct(string $template, array $variables = [])
|
||||
{
|
||||
$this->template = $template;
|
||||
$this->variables = $variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* By default, unmatched templates will be removed from the output, set this to keep them
|
||||
*/
|
||||
public function keepEmptyTemplates(): SimpleTemplate
|
||||
{
|
||||
$this->keepEmpty = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a variable to the set of possible substitutions
|
||||
*/
|
||||
public function setVariable(string $key, string $value): SimpleTemplate
|
||||
{
|
||||
$this->variables[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instead of using the given variables to replace {{ var }}
|
||||
* send the matched variable to this callback, which will return a string to replace it
|
||||
*/
|
||||
public function replaceWith(callable $callback): SimpleTemplate
|
||||
{
|
||||
$this->callback = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and parse a simple template
|
||||
*
|
||||
* @param string $template
|
||||
* @param array $variables
|
||||
* @return string
|
||||
*/
|
||||
public static function parse(string $template, array $variables): string
|
||||
{
|
||||
return (string) new static($template, $variables);
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return preg_replace_callback($this->regex, $this->callback ?? function ($matches) {
|
||||
$replacement = $this->variables[$matches[1]] ?? ($this->keepEmpty ? $matches[0] : '');
|
||||
if (! StringHelpers::isStringable($replacement)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $replacement;
|
||||
}, $this->template);
|
||||
}
|
||||
}
|
@ -3820,21 +3820,6 @@ parameters:
|
||||
count: 1
|
||||
path: LibreNMS/OS.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\OS\\:\\:parseTemplate\\(\\) has no return typehint specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/OS.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\OS\\:\\:parseTemplate\\(\\) has parameter \\$data with no typehint specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/OS.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\OS\\:\\:parseTemplate\\(\\) has parameter \\$template with no typehint specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/OS.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\OS\\:\\:persistGraphs\\(\\) has no return typehint specified\\.$#"
|
||||
count: 1
|
||||
@ -4375,21 +4360,6 @@ parameters:
|
||||
count: 1
|
||||
path: LibreNMS/OS/Shared/Cisco.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\OS\\\\Shared\\\\Cisco\\:\\:parseTemplate\\(\\) has no return typehint specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/OS/Shared/Cisco.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\OS\\\\Shared\\\\Cisco\\:\\:parseTemplate\\(\\) has parameter \\$data with no typehint specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/OS/Shared/Cisco.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\OS\\\\Shared\\\\Cisco\\:\\:parseTemplate\\(\\) has parameter \\$template with no typehint specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/OS/Shared/Cisco.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\OS\\\\Shared\\\\Cisco\\:\\:pollSlas\\(\\) has no return typehint specified\\.$#"
|
||||
count: 1
|
||||
@ -4530,21 +4500,6 @@ parameters:
|
||||
count: 1
|
||||
path: LibreNMS/OS/Shared/Unix.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\OS\\\\Shared\\\\Unix\\:\\:parseTemplate\\(\\) has no return typehint specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/OS/Shared/Unix.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\OS\\\\Shared\\\\Unix\\:\\:parseTemplate\\(\\) has parameter \\$data with no typehint specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/OS/Shared/Unix.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\OS\\\\Shared\\\\Unix\\:\\:parseTemplate\\(\\) has parameter \\$template with no typehint specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/OS/Shared/Unix.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\OS\\\\Shared\\\\Unix\\:\\:translateSysObjectID\\(\\) has no return typehint specified\\.$#"
|
||||
count: 1
|
||||
@ -4625,21 +4580,6 @@ parameters:
|
||||
count: 1
|
||||
path: LibreNMS/OS/Shared/Zyxel.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\OS\\\\Shared\\\\Zyxel\\:\\:parseTemplate\\(\\) has no return typehint specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/OS/Shared/Zyxel.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\OS\\\\Shared\\\\Zyxel\\:\\:parseTemplate\\(\\) has parameter \\$data with no typehint specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/OS/Shared/Zyxel.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\OS\\\\Shared\\\\Zyxel\\:\\:parseTemplate\\(\\) has parameter \\$template with no typehint specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/OS/Shared/Zyxel.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\OS\\\\Shared\\\\Zyxel\\:\\:translateSysObjectID\\(\\) has no return typehint specified\\.$#"
|
||||
count: 1
|
||||
|
@ -35,7 +35,7 @@ class StringHelperTest extends TestCase
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testInferEncoding()
|
||||
public function testInferEncoding(): void
|
||||
{
|
||||
$this->assertEquals(null, StringHelpers::inferEncoding(null));
|
||||
$this->assertEquals('', StringHelpers::inferEncoding(''));
|
||||
@ -48,4 +48,31 @@ class StringHelperTest extends TestCase
|
||||
config(['app.charset' => 'Shift_JIS']);
|
||||
$this->assertEquals('コンサート', StringHelpers::inferEncoding(base64_decode('g1KDk4NUgVuDZw==')));
|
||||
}
|
||||
|
||||
public function testIsStringable(): void
|
||||
{
|
||||
$this->assertTrue(StringHelpers::isStringable(null));
|
||||
$this->assertTrue(StringHelpers::isStringable(''));
|
||||
$this->assertTrue(StringHelpers::isStringable('string'));
|
||||
$this->assertTrue(StringHelpers::isStringable(-1));
|
||||
$this->assertTrue(StringHelpers::isStringable(1.0));
|
||||
$this->assertTrue(StringHelpers::isStringable(false));
|
||||
|
||||
$this->assertFalse(StringHelpers::isStringable([]));
|
||||
$this->assertFalse(StringHelpers::isStringable((object) []));
|
||||
|
||||
$stringable = new class
|
||||
{
|
||||
public function __toString()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
};
|
||||
$this->assertTrue(StringHelpers::isStringable($stringable));
|
||||
|
||||
$nonstringable = new class
|
||||
{
|
||||
};
|
||||
$this->assertFalse(StringHelpers::isStringable($nonstringable));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user