mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
Fix api transport mult-line parsing (#13469)
* API transport fix parsing parse templates after parsing user options, not before * API transport tests * fix style and lint * remove accidental item * fix more type issues
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
namespace LibreNMS\Alert;
|
||||
|
||||
use App\Models\AlertTransport;
|
||||
use App\View\SimpleTemplate;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Support\Str;
|
||||
use LibreNMS\Config;
|
||||
@@ -24,6 +25,24 @@ abstract class Transport implements TransportInterface
|
||||
return new $class();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all available transports
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function list(): array
|
||||
{
|
||||
$list = [];
|
||||
foreach (glob(base_path('LibreNMS/Alert/Transport/*.php')) as $file) {
|
||||
$transport = strtolower(basename($file, '.php'));
|
||||
$class = self::getClass($transport);
|
||||
$instance = new $class;
|
||||
$list[$transport] = $instance->name();
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transport constructor.
|
||||
*
|
||||
@@ -63,15 +82,16 @@ abstract class Transport implements TransportInterface
|
||||
* Helper function to parse free form text box defined in ini style to key value pairs
|
||||
*
|
||||
* @param string $input
|
||||
* @param array $replacements for SimpleTemplate if desired
|
||||
* @return array
|
||||
*/
|
||||
protected function parseUserOptions(string $input): array
|
||||
protected function parseUserOptions(string $input, array $replacements = []): array
|
||||
{
|
||||
$options = [];
|
||||
foreach (preg_split('/\\r\\n|\\r|\\n/', $input, -1, PREG_SPLIT_NO_EMPTY) as $option) {
|
||||
if (Str::contains($option, '=')) {
|
||||
[$k, $v] = explode('=', $option, 2);
|
||||
$options[$k] = trim($v);
|
||||
$options[$k] = empty($replacements) ? trim($v) : SimpleTemplate::parse(trim($v), $replacements);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -52,11 +52,11 @@ class Api extends Transport
|
||||
$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;
|
||||
$request_heads = $this->parseUserOptions(SimpleTemplate::parse($headers, $obj));
|
||||
$request_heads = $this->parseUserOptions($headers, $obj);
|
||||
//get each line of key-values and process the variables for Options;
|
||||
$query = $this->parseUserOptions(SimpleTemplate::parse($options, $obj));
|
||||
$query = $this->parseUserOptions($options, $obj);
|
||||
|
||||
$client = new \GuzzleHttp\Client();
|
||||
$client = app(\GuzzleHttp\Client::class);
|
||||
$request_opts['proxy'] = Proxy::forGuzzle();
|
||||
if (isset($auth) && ! empty($auth[0])) {
|
||||
$request_opts['auth'] = $auth;
|
||||
|
49
database/factories/AlertTransportFactory.php
Normal file
49
database/factories/AlertTransportFactory.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\AlertTransport;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use LibreNMS\Alert\Transport;
|
||||
|
||||
class AlertTransportFactory extends Factory
|
||||
{
|
||||
protected $model = AlertTransport::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'transport_name' => $this->faker->text(30),
|
||||
'transport_type' => $this->faker->randomElement(Transport::list()),
|
||||
'is_default' => 0,
|
||||
'transport_config' => '',
|
||||
];
|
||||
}
|
||||
|
||||
public function api(
|
||||
string $options = '',
|
||||
string $method = 'get',
|
||||
string $body = '',
|
||||
string $url = 'https://librenms.org',
|
||||
string $headers = 'test=header',
|
||||
string $username = '',
|
||||
string $password = ''
|
||||
): AlertTransportFactory {
|
||||
$config = [
|
||||
'api-method' => $method,
|
||||
'api-url' => $url,
|
||||
'api-options' => $options,
|
||||
'api-headers' => $headers,
|
||||
'api-body' => $body,
|
||||
'api-auth-username' => $username,
|
||||
'api-auth-password' => $password,
|
||||
];
|
||||
|
||||
return $this->state(function () use ($config) {
|
||||
return [
|
||||
'transport_type' => 'api',
|
||||
'transport_config' => $config,
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
@@ -11,7 +11,7 @@
|
||||
* the source code distribution for details.
|
||||
*/
|
||||
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Alert\Transport;
|
||||
|
||||
if (Auth::user()->hasGlobalAdmin()) {
|
||||
?>
|
||||
@@ -42,19 +42,9 @@ if (Auth::user()->hasGlobalAdmin()) {
|
||||
<?php
|
||||
|
||||
// Create list of transport
|
||||
$transport_dir = Config::get('install_dir') . '/LibreNMS/Alert/Transport';
|
||||
$transports_list = [];
|
||||
foreach (scandir($transport_dir) as $transport) {
|
||||
$transport = strstr($transport, '.', true);
|
||||
if (empty($transport)) {
|
||||
continue;
|
||||
}
|
||||
$class = "\LibreNMS\Alert\Transport\\$transport";
|
||||
$instance = new $class;
|
||||
$transports_list[$transport] = $instance->name();
|
||||
}
|
||||
$transports_list = Transport::list();
|
||||
foreach ($transports_list as $transport => $name) {
|
||||
echo '<option value="' . strtolower($transport) . '-form">' . $name . '</option>';
|
||||
echo '<option value="' . $transport . '-form">' . $name . '</option>';
|
||||
} ?>
|
||||
</select>
|
||||
</div>
|
||||
@@ -70,16 +60,16 @@ if (Auth::user()->hasGlobalAdmin()) {
|
||||
|
||||
$switches = []; // store names of bootstrap switches
|
||||
foreach ($transports_list as $transport => $name) {
|
||||
$class = 'LibreNMS\\Alert\\Transport\\' . $transport;
|
||||
$class = Transport::getClass($transport);
|
||||
|
||||
if (! method_exists($class, 'configTemplate')) {
|
||||
// Skip since support has not been added
|
||||
continue;
|
||||
}
|
||||
|
||||
echo '<form method="post" role="form" id="' . strtolower($transport) . '-form" class="form-horizontal transport">';
|
||||
echo '<form method="post" role="form" id="' . $transport . '-form" class="form-horizontal transport">';
|
||||
echo csrf_field();
|
||||
echo '<input type="hidden" name="transport-type" value="' . strtolower($transport) . '">';
|
||||
echo '<input type="hidden" name="transport-type" value="' . $transport . '">';
|
||||
|
||||
$tmp = call_user_func($class . '::configTemplate');
|
||||
|
||||
|
@@ -9,7 +9,7 @@ if ($request->has('oauthtransport')) {
|
||||
|
||||
if ($validator->passes()) {
|
||||
$transport_name = $request->get('oauthtransport');
|
||||
$class = 'LibreNMS\\Alert\\Transport\\' . $transport_name;
|
||||
$class = \LibreNMS\Alert\Transport::getClass($transport_name);
|
||||
if (class_exists($class)) {
|
||||
$transport = app($class);
|
||||
if ($transport->handleOauth($request)) {
|
||||
|
80
tests/Traits/MockGuzzleClient.php
Normal file
80
tests/Traits/MockGuzzleClient.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace LibreNMS\Tests\Traits;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Handler\MockHandler;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Middleware;
|
||||
|
||||
/**
|
||||
* @mixin \LibreNMS\Tests\TestCase
|
||||
*/
|
||||
trait MockGuzzleClient
|
||||
{
|
||||
/**
|
||||
* @var MockHandler
|
||||
*/
|
||||
private $guzzleMockHandler;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $guzzleConfig;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $guzzleHistory = [];
|
||||
|
||||
/**
|
||||
* Create a Guzzle MockHandler and bind Client with the handler to the Laravel container
|
||||
*
|
||||
* @param array $queue Sequential Responses to give to the client.
|
||||
* @param array $config Guzzle config settings.
|
||||
*/
|
||||
public function mockGuzzleClient(array $queue, array $config = []): MockHandler
|
||||
{
|
||||
$this->guzzleConfig = $config;
|
||||
$this->guzzleMockHandler = new MockHandler($queue);
|
||||
|
||||
$this->app->bind(Client::class, function () {
|
||||
$handlerStack = HandlerStack::create($this->guzzleMockHandler);
|
||||
$handlerStack->push(Middleware::history($this->guzzleHistory));
|
||||
|
||||
return new Client(array_merge($this->guzzleConfig, ['handler' => $handlerStack]));
|
||||
});
|
||||
|
||||
return $this->guzzleMockHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request and response history to inspect
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function guzzleHistory(): array
|
||||
{
|
||||
return $this->guzzleHistory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request history to inspect
|
||||
*
|
||||
* @return \GuzzleHttp\Psr7\Request[]
|
||||
*/
|
||||
public function guzzleRequestHistory(): array
|
||||
{
|
||||
return array_column($this->guzzleHistory, 'request');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response history to inspect
|
||||
*
|
||||
* @return \GuzzleHttp\Psr7\Response[]
|
||||
*/
|
||||
public function guzzleResponseHistory(): array
|
||||
{
|
||||
return array_column($this->guzzleHistory, 'response');
|
||||
}
|
||||
}
|
62
tests/Unit/ApiTransportTest.php
Normal file
62
tests/Unit/ApiTransportTest.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace LibreNMS\Tests\Unit;
|
||||
|
||||
use App\Models\AlertTransport;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Tests\TestCase;
|
||||
use LibreNMS\Tests\Traits\MockGuzzleClient;
|
||||
|
||||
class ApiTransportTest extends TestCase
|
||||
{
|
||||
use MockGuzzleClient;
|
||||
|
||||
public function testGetMultilineVariables(): void
|
||||
{
|
||||
/** @var AlertTransport $transport */
|
||||
$transport = AlertTransport::factory()->api('text={{ $msg }}')->make();
|
||||
|
||||
$this->mockGuzzleClient([
|
||||
new Response(200),
|
||||
]);
|
||||
|
||||
$obj = ['msg' => "This is a multi-line\nalert."];
|
||||
$opts = Config::get('alert.transports.' . $transport->transport_type);
|
||||
$result = $transport->instance()->deliverAlert($obj, $opts);
|
||||
|
||||
$this->assertTrue($result);
|
||||
|
||||
$history = $this->guzzleRequestHistory();
|
||||
$this->assertCount(1, $history);
|
||||
$this->assertEquals('GET', $history[0]->getMethod());
|
||||
$this->assertEquals('text=This%20is%20a%20multi-line%0Aalert.', $history[0]->getUri()->getQuery());
|
||||
}
|
||||
|
||||
public function testPostMultilineVariables(): void
|
||||
{
|
||||
/** @var AlertTransport $transport */
|
||||
$transport = AlertTransport::factory()->api(
|
||||
'text={{ $msg }}',
|
||||
'post',
|
||||
'bodytext={{ $msg }}',
|
||||
)->make();
|
||||
|
||||
$this->mockGuzzleClient([
|
||||
new Response(200),
|
||||
]);
|
||||
|
||||
$obj = ['msg' => "This is a post multi-line\nalert."];
|
||||
$opts = Config::get('alert.transports.' . $transport->transport_type);
|
||||
$result = $transport->instance()->deliverAlert($obj, $opts);
|
||||
|
||||
$this->assertTrue($result);
|
||||
|
||||
$history = $this->guzzleRequestHistory();
|
||||
$this->assertCount(1, $history);
|
||||
$this->assertEquals('POST', $history[0]->getMethod());
|
||||
// FUBAR
|
||||
$this->assertEquals('text=This%20is%20a%20post%20multi-line%0Aalert.', $history[0]->getUri()->getQuery());
|
||||
$this->assertEquals("bodytext=This is a post multi-line\nalert.", (string) $history[0]->getBody());
|
||||
}
|
||||
}
|
@@ -25,22 +25,21 @@
|
||||
|
||||
namespace LibreNMS\Tests\Unit\Data;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Handler\MockHandler;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Middleware;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Data\Store\Prometheus;
|
||||
use LibreNMS\Tests\TestCase;
|
||||
use LibreNMS\Tests\Traits\MockGuzzleClient;
|
||||
|
||||
/**
|
||||
* @group datastores
|
||||
*/
|
||||
class PrometheusStoreTest extends TestCase
|
||||
{
|
||||
use MockGuzzleClient;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
@@ -51,13 +50,12 @@ class PrometheusStoreTest extends TestCase
|
||||
|
||||
public function testFailWrite()
|
||||
{
|
||||
$stack = HandlerStack::create(new MockHandler([
|
||||
$this->mockGuzzleClient([
|
||||
new Response(422, [], 'Bad response'),
|
||||
new RequestException('Exception thrown', new Request('POST', 'test')),
|
||||
]));
|
||||
]);
|
||||
|
||||
$client = new Client(['handler' => $stack]);
|
||||
$prometheus = new Prometheus($client);
|
||||
$prometheus = app(Prometheus::class);
|
||||
|
||||
\Log::shouldReceive('debug');
|
||||
\Log::shouldReceive('error')->once()->with("Prometheus Exception: Client error: `POST http://fake:9999/metrics/job/librenms/instance/test/measurement/none` resulted in a `422 Unprocessable Entity` response:\nBad response\n");
|
||||
@@ -68,16 +66,11 @@ class PrometheusStoreTest extends TestCase
|
||||
|
||||
public function testSimpleWrite()
|
||||
{
|
||||
$stack = HandlerStack::create(new MockHandler([
|
||||
$this->mockGuzzleClient([
|
||||
new Response(200),
|
||||
]));
|
||||
]);
|
||||
|
||||
$container = [];
|
||||
$history = Middleware::history($container);
|
||||
|
||||
$stack->push($history);
|
||||
$client = new Client(['handler' => $stack]);
|
||||
$prometheus = new Prometheus($client);
|
||||
$prometheus = app(Prometheus::class);
|
||||
|
||||
$device = ['hostname' => 'testhost'];
|
||||
$measurement = 'testmeasure';
|
||||
@@ -89,15 +82,12 @@ class PrometheusStoreTest extends TestCase
|
||||
|
||||
$prometheus->put($device, $measurement, $tags, $fields);
|
||||
|
||||
$this->assertCount(1, $container, 'Did not receive the expected number of requests');
|
||||
|
||||
/** @var Request $request */
|
||||
$request = $container[0]['request'];
|
||||
|
||||
$this->assertEquals('POST', $request->getMethod());
|
||||
$this->assertEquals('/metrics/job/librenms/instance/testhost/measurement/testmeasure/ifName/testifname/type/testtype', $request->getUri()->getPath());
|
||||
$this->assertEquals('fake', $request->getUri()->getHost());
|
||||
$this->assertEquals(9999, $request->getUri()->getPort());
|
||||
$this->assertEquals("ifIn 234234\nifOut 53453\n", (string) $request->getBody());
|
||||
$history = $this->guzzleRequestHistory();
|
||||
$this->assertCount(1, $history, 'Did not receive the expected number of requests');
|
||||
$this->assertEquals('POST', $history[0]->getMethod());
|
||||
$this->assertEquals('/metrics/job/librenms/instance/testhost/measurement/testmeasure/ifName/testifname/type/testtype', $history[0]->getUri()->getPath());
|
||||
$this->assertEquals('fake', $history[0]->getUri()->getHost());
|
||||
$this->assertEquals(9999, $history[0]->getUri()->getPort());
|
||||
$this->assertEquals("ifIn 234234\nifOut 53453\n", (string) $history[0]->getBody());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user