mirror of
https://github.com/librenms/librenms.git
synced 2024-09-21 02:18:39 +00:00
lnms config:set ability to set os settings (#13151)
* lnms config:set works for os settings validate against os schema (gives us path and value validation) fix unset in config:set json formatted output in config:get to match input parsing * inline errors * Check that OS exists * Fix lock file * Set param type * correct method name, it no longer returns a boolean * rename --json to --dump tests and fixes * fix whitespace * missed one whitespace * typehints * add connection typehint * try again
This commit is contained in:
parent
fcb1f2d6fa
commit
e2d1bfff54
@ -24,7 +24,7 @@ class GetConfigCommand extends LnmsCommand
|
||||
parent::__construct();
|
||||
|
||||
$this->addArgument('setting', InputArgument::OPTIONAL);
|
||||
$this->addOption('json');
|
||||
$this->addOption('dump');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -42,7 +42,7 @@ class GetConfigCommand extends LnmsCommand
|
||||
Config::forget("os.{$matches['os']}.definition_loaded");
|
||||
}
|
||||
|
||||
if ($this->option('json')) {
|
||||
if ($this->option('dump')) {
|
||||
$this->line($setting ? json_encode(Config::get($setting)) : Config::toJson());
|
||||
|
||||
return 0;
|
||||
@ -55,7 +55,7 @@ class GetConfigCommand extends LnmsCommand
|
||||
if (Config::has($setting)) {
|
||||
$output = Config::get($setting);
|
||||
if (! is_string($output)) {
|
||||
$output = var_export($output, true);
|
||||
$output = json_encode($output, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
$this->line($output);
|
||||
|
@ -6,9 +6,13 @@ use App\Console\Commands\Traits\CompletesConfigArgument;
|
||||
use App\Console\LnmsCommand;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use JsonSchema\Constraints\Constraint;
|
||||
use JsonSchema\Exception\ValidationException;
|
||||
use JsonSchema\Validator;
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\DB\Eloquent;
|
||||
use LibreNMS\Util\DynamicConfig;
|
||||
use LibreNMS\Util\OS;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
|
||||
class SetConfigCommand extends LnmsCommand
|
||||
@ -43,7 +47,17 @@ class SetConfigCommand extends LnmsCommand
|
||||
$force = $this->option('ignore-checks');
|
||||
$parent = null;
|
||||
|
||||
if (! $definition->isValidSetting($setting)) {
|
||||
if (preg_match('/^os\.(?<os>[a-z_\-]+)\.(?<setting>.*)$/', $setting, $matches)) {
|
||||
$os = $matches['os'];
|
||||
try {
|
||||
$this->validateOsSetting($os, $matches['setting'], $value);
|
||||
} catch (ValidationException $e) {
|
||||
$this->error(trans('commands.config:set.errors.invalid'));
|
||||
$this->line($e->getMessage());
|
||||
|
||||
return 2;
|
||||
}
|
||||
} elseif (! $definition->isValidSetting($setting)) {
|
||||
$parent = $this->findParentSetting($definition, $setting);
|
||||
if (! $force && ! $parent) {
|
||||
$this->error(trans('commands.config:set.errors.invalid'));
|
||||
@ -87,7 +101,7 @@ class SetConfigCommand extends LnmsCommand
|
||||
}
|
||||
|
||||
// handle setting value inside multi-dimensional array
|
||||
if ($parent) {
|
||||
if ($parent && $parent !== $setting) {
|
||||
$parent_data = Config::get($parent);
|
||||
Arr::set($parent_data, $this->getChildPath($setting, $parent), $value);
|
||||
$value = $parent_data;
|
||||
@ -95,7 +109,10 @@ class SetConfigCommand extends LnmsCommand
|
||||
}
|
||||
|
||||
$configItem = $definition->get($setting);
|
||||
if (! $force && ! $configItem->checkValue($value)) {
|
||||
if (! $force
|
||||
&& empty($os) // if os is set, value was already validated against os config
|
||||
&& ! $configItem->checkValue($value)
|
||||
) {
|
||||
$message = ($configItem->type || $configItem->validate)
|
||||
? $configItem->getValidationMessage($value)
|
||||
: trans('commands.config:set.errors.no-validation', ['setting' => $setting]);
|
||||
@ -118,7 +135,7 @@ class SetConfigCommand extends LnmsCommand
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function juggleType(string $value)
|
||||
private function juggleType(?string $value)
|
||||
{
|
||||
$json = json_decode($value, true);
|
||||
|
||||
@ -185,4 +202,54 @@ class SetConfigCommand extends LnmsCommand
|
||||
Arr::forget($data, $matches);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $os
|
||||
* @param string $setting
|
||||
* @param mixed $value
|
||||
* @throws \JsonSchema\Exception\ValidationException
|
||||
*/
|
||||
private function validateOsSetting(string $os, string $setting, $value)
|
||||
{
|
||||
// prep data to be validated
|
||||
OS::loadDefinition($os);
|
||||
$os_data = \LibreNMS\Config::get("os.$os");
|
||||
if ($os_data === null) {
|
||||
throw new ValidationException(trans('commands.config:set.errors.invalid_os', ['os' => $os]));
|
||||
}
|
||||
Arr::set($os_data, $setting, $this->juggleType($value));
|
||||
unset($os_data['definition_loaded']);
|
||||
|
||||
$validator = new Validator;
|
||||
$validator->validate(
|
||||
$os_data,
|
||||
(object) ['$ref' => 'file://' . base_path('/misc/os_schema.json')],
|
||||
Constraint::CHECK_MODE_TYPE_CAST
|
||||
);
|
||||
|
||||
$code = 0;
|
||||
|
||||
$errors = collect($validator->getErrors())->filter(function ($error) use ($value, &$code) {
|
||||
if ($error['constraint'] == 'additionalProp') {
|
||||
$code = 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// only check type if value is set (otherwise we are unsetting it)
|
||||
if (! empty($value) && $error['constraint'] == 'type') {
|
||||
if ($code === 0) {
|
||||
$code = 2; // wrong path takes precedence over wrong type
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if ($errors->isNotEmpty()) {
|
||||
throw new ValidationException($errors->pluck('message')->implode(PHP_EOL), $code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,7 @@
|
||||
"genealabs/laravel-caffeine": "^8.0",
|
||||
"guzzlehttp/guzzle": "^7.0.1",
|
||||
"influxdb/influxdb-php": "^1.14",
|
||||
"justinrainbow/json-schema": "^5.2",
|
||||
"laravel/framework": "^8.12",
|
||||
"laravel/tinker": "^2.5",
|
||||
"laravel/ui": "^3.0",
|
||||
@ -59,7 +60,6 @@
|
||||
"facade/ignition": "^2.5",
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"friendsofphp/php-cs-fixer": "^2.16",
|
||||
"justinrainbow/json-schema": "^5.2",
|
||||
"laravel/dusk": "^6.15",
|
||||
"mockery/mockery": "^1.4.2",
|
||||
"nunomaduro/collision": "^5.0",
|
||||
|
144
composer.lock
generated
144
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "7e036d2ebed332bf23de83ae6dd716a3",
|
||||
"content-hash": "7971c0ef0ed8ea90a3df66dbd8f05295",
|
||||
"packages": [
|
||||
{
|
||||
"name": "amenadiel/jpgraph",
|
||||
@ -1729,6 +1729,76 @@
|
||||
},
|
||||
"time": "2020-09-11T11:05:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "justinrainbow/json-schema",
|
||||
"version": "5.2.11",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/justinrainbow/json-schema.git",
|
||||
"reference": "2ab6744b7296ded80f8cc4f9509abbff393399aa"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/2ab6744b7296ded80f8cc4f9509abbff393399aa",
|
||||
"reference": "2ab6744b7296ded80f8cc4f9509abbff393399aa",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1",
|
||||
"json-schema/json-schema-test-suite": "1.2.0",
|
||||
"phpunit/phpunit": "^4.8.35"
|
||||
},
|
||||
"bin": [
|
||||
"bin/validate-json"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "5.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"JsonSchema\\": "src/JsonSchema/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Bruno Prieto Reis",
|
||||
"email": "bruno.p.reis@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Justin Rainbow",
|
||||
"email": "justin.rainbow@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Igor Wiedler",
|
||||
"email": "igor@wiedler.ch"
|
||||
},
|
||||
{
|
||||
"name": "Robert Schönthal",
|
||||
"email": "seroscho@googlemail.com"
|
||||
}
|
||||
],
|
||||
"description": "A library to validate a json schema.",
|
||||
"homepage": "https://github.com/justinrainbow/json-schema",
|
||||
"keywords": [
|
||||
"json",
|
||||
"schema"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/justinrainbow/json-schema/issues",
|
||||
"source": "https://github.com/justinrainbow/json-schema/tree/5.2.11"
|
||||
},
|
||||
"time": "2021-07-22T09:24:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v8.49.2",
|
||||
@ -8260,76 +8330,6 @@
|
||||
},
|
||||
"time": "2020-07-09T08:09:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "justinrainbow/json-schema",
|
||||
"version": "5.2.10",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/justinrainbow/json-schema.git",
|
||||
"reference": "2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b",
|
||||
"reference": "2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1",
|
||||
"json-schema/json-schema-test-suite": "1.2.0",
|
||||
"phpunit/phpunit": "^4.8.35"
|
||||
},
|
||||
"bin": [
|
||||
"bin/validate-json"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "5.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"JsonSchema\\": "src/JsonSchema/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Bruno Prieto Reis",
|
||||
"email": "bruno.p.reis@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Justin Rainbow",
|
||||
"email": "justin.rainbow@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Igor Wiedler",
|
||||
"email": "igor@wiedler.ch"
|
||||
},
|
||||
{
|
||||
"name": "Robert Schönthal",
|
||||
"email": "seroscho@googlemail.com"
|
||||
}
|
||||
],
|
||||
"description": "A library to validate a json schema.",
|
||||
"homepage": "https://github.com/justinrainbow/json-schema",
|
||||
"keywords": [
|
||||
"json",
|
||||
"schema"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/justinrainbow/json-schema/issues",
|
||||
"source": "https://github.com/justinrainbow/json-schema/tree/5.2.10"
|
||||
},
|
||||
"time": "2020-05-27T16:41:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/dusk",
|
||||
"version": "v6.15.1",
|
||||
@ -11460,5 +11460,5 @@
|
||||
"ext-xml": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.0.0"
|
||||
"plugin-api-version": "2.1.0"
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ lnms config:set snmp.community
|
||||
> yes
|
||||
|
||||
|
||||
lnms config:get snmp.community --json
|
||||
lnms config:get snmp.community
|
||||
["public"]
|
||||
```
|
||||
|
||||
|
@ -7,7 +7,7 @@ return [
|
||||
'setting' => 'setting to get value of in dot notation (example: snmp.community.0)',
|
||||
],
|
||||
'options' => [
|
||||
'json' => 'Output setting or entire config as json',
|
||||
'dump' => 'Output the entire config as json',
|
||||
],
|
||||
],
|
||||
'config:set' => [
|
||||
@ -24,7 +24,8 @@ return [
|
||||
'errors' => [
|
||||
'append' => 'Cannot append to non-array setting',
|
||||
'failed' => 'Failed to set :setting',
|
||||
'invalid' => 'This is not a valid setting. Please check your spelling',
|
||||
'invalid' => 'This is not a valid setting. Please check your input',
|
||||
'invalid_os' => 'Specified OS (:os) does not exist',
|
||||
'nodb' => 'Database is not connected',
|
||||
'no-validation' => 'Cannot set :setting, it is missing validation definition.',
|
||||
],
|
||||
|
135
tests/Feature/Commands/TestConfigCommands.php
Normal file
135
tests/Feature/Commands/TestConfigCommands.php
Normal file
@ -0,0 +1,135 @@
|
||||
<?php
|
||||
/*
|
||||
* TestSetConfigCommand.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* 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 LibreNMS\Tests\Feature\Commands;
|
||||
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Tests\InMemoryDbTestCase;
|
||||
|
||||
class TestConfigCommands extends InMemoryDbTestCase
|
||||
{
|
||||
public function testSetting(): void
|
||||
{
|
||||
// simple
|
||||
Config::set('login_message', null);
|
||||
$this->assertCliSets('login_message', 'hello');
|
||||
|
||||
// nested
|
||||
Config::forget('allow_entity_sensor.amperes');
|
||||
$this->assertCliSets('allow_entity_sensor.amperes', 'false');
|
||||
|
||||
// set inside
|
||||
$this->assertCliGets('auth_ldap_groups.somegroup', null);
|
||||
$this->artisan('config:set', ['setting' => 'auth_ldap_groups.somegroup', 'value' => '{"level": 3}'])->assertExitCode(0);
|
||||
$this->assertCliGets('auth_ldap_groups.somegroup', ['level' => 3]);
|
||||
$this->artisan('config:set', ['setting' => 'auth_ldap_groups.somegroup'])
|
||||
->expectsConfirmation(trans('commands.config:set.forget_from', ['path' => 'somegroup', 'parent' => 'auth_ldap_groups']), 'yes')
|
||||
->assertExitCode(0);
|
||||
|
||||
// test append
|
||||
$community = Config::get('snmp.community');
|
||||
$this->assertCliGets('snmp.community', $community);
|
||||
$community[] = 'extra_community';
|
||||
$this->artisan('config:set', ['setting' => 'snmp.community.+', 'value' => 'extra_community'])->assertExitCode(0);
|
||||
$this->assertCliGets('snmp.community', $community);
|
||||
|
||||
// os bool
|
||||
$this->assertCliSets('os.ios.rfc1628_compat', true);
|
||||
|
||||
// os array and append
|
||||
$this->assertCliSets('os.netonix.bad_iftype', ['ethernet', 'psuedowire']);
|
||||
// $this->artisan('config:set', ['setting' => 'os.netonix.bad_iftype.+', 'value' => 'other'])->assertExitCode(0);
|
||||
// $this->assertCliGets('os.netonix.bad_iftype', ['ethernet', 'psuedowire', 'other']);
|
||||
|
||||
// dump
|
||||
$this->artisan('config:get', ['--dump' => true])
|
||||
->expectsOutput(Config::toJson())
|
||||
->assertExitCode(0);
|
||||
}
|
||||
|
||||
public function testInvalidSetting(): void
|
||||
{
|
||||
// non-existent setting
|
||||
$this->artisan('config:set', ['setting' => 'this_will_never_be.a.setting'])
|
||||
->assertExitCode(2);
|
||||
|
||||
// invalid type
|
||||
$this->artisan('config:set', ['setting' => 'alert_rule.interval', 'value' => 'string', '--no-ansi' => true])
|
||||
->expectsOutput(trans('settings.validate.integer', ['value' => '"string"']))
|
||||
->assertExitCode(2);
|
||||
|
||||
// non-existent os
|
||||
$this->artisan('config:set', ['setting' => 'os.someos.this_will_never_be.a.setting'])
|
||||
->expectsOutput(trans('commands.config:set.errors.invalid_os', ['os' => 'someos']))
|
||||
->assertExitCode(2);
|
||||
|
||||
// non-existent os setting
|
||||
$this->artisan('config:set', ['setting' => 'os.ios.this_will_never_be.a.setting'])
|
||||
->doesntExpectOutput(trans('commands.config:set.errors.invalid_os', ['os' => 'ios']))
|
||||
->assertExitCode(2);
|
||||
|
||||
// append to non-array
|
||||
Config::set('login_message', 'blah');
|
||||
$message = Config::get('login_message');
|
||||
$this->artisan('config:set', ['setting' => 'login_message.+', 'value' => 'something', '--no-ansi' => true])
|
||||
->expectsOutput(trans('commands.config:set.errors.append'))
|
||||
->assertExitCode(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $setting
|
||||
* @param mixed $expected
|
||||
*/
|
||||
private function assertCliSets(string $setting, $expected): void
|
||||
{
|
||||
$this->assertCliGets($setting, null);
|
||||
$this->artisan('config:set', ['setting' => $setting, 'value' => json_encode($expected)])->assertExitCode(0);
|
||||
$this->assertCliGets($setting, $expected);
|
||||
$this->artisan('config:set', ['setting' => $setting])
|
||||
->expectsQuestion(trans('commands.config:set.confirm', ['setting' => $setting]), true)
|
||||
->assertExitCode(0);
|
||||
$this->assertCliGets($setting, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $setting
|
||||
* @param mixed $expected
|
||||
*/
|
||||
private function assertCliGets(string $setting, $expected): void
|
||||
{
|
||||
$this->assertSame($expected, \LibreNMS\Config::get($setting));
|
||||
|
||||
$command = $this->artisan('config:get', ['setting' => $setting]);
|
||||
if ($expected === null) {
|
||||
$command->assertExitCode(1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$command->assertExitCode(0)
|
||||
->expectsOutput(is_string($expected) ? $expected : json_encode($expected, JSON_PRETTY_PRINT))
|
||||
->assertExitCode(0);
|
||||
}
|
||||
}
|
42
tests/InMemoryDbTestCase.php
Normal file
42
tests/InMemoryDbTestCase.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/*
|
||||
* InMemoryDbTestCase.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* 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 LibreNMS\Tests;
|
||||
|
||||
class InMemoryDbTestCase extends TestCase
|
||||
{
|
||||
/** @var string */
|
||||
protected $connection = 'testing_memory';
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->artisan('migrate:fresh', ['--database' => $this->connection]);
|
||||
|
||||
$current = config('database.default');
|
||||
config(['database.default' => $this->connection]);
|
||||
\DB::purge($current);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user