mirror of
https://github.com/librenms/librenms.git
synced 2024-09-21 10:28:13 +00:00
Automatic fixes for validation failures (#13930)
* Automatic fixes for validations * webui * lint fixes * Fix an install issue with ConfigSeeder requesting cli input in web page. * Do not use c_echo in validate.php print_fail()
This commit is contained in:
parent
46899d1643
commit
7a0d604cdd
43
LibreNMS/Interfaces/Validation.php
Normal file
43
LibreNMS/Interfaces/Validation.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/*
|
||||
* Validation.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 2022 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Interfaces;
|
||||
|
||||
use LibreNMS\ValidationResult;
|
||||
|
||||
interface Validation
|
||||
{
|
||||
/**
|
||||
* Validate this module.
|
||||
*/
|
||||
public function validate(): ValidationResult;
|
||||
|
||||
/**
|
||||
* If this validation is enabled or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function enabled(): bool;
|
||||
}
|
36
LibreNMS/Interfaces/ValidationFixer.php
Normal file
36
LibreNMS/Interfaces/ValidationFixer.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/*
|
||||
* ValidationFixer.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 2022 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Interfaces;
|
||||
|
||||
interface ValidationFixer
|
||||
{
|
||||
/**
|
||||
* Fix the failed validation result. Take care not to break user installs.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function fix(): bool;
|
||||
}
|
@ -32,29 +32,21 @@ interface ValidationGroup
|
||||
/**
|
||||
* Validate this module.
|
||||
* To return ValidationResults, call ok, warn, fail, or result methods on the $validator
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
public function validate(Validator $validator);
|
||||
public function validate(Validator $validator): void;
|
||||
|
||||
/**
|
||||
* Returns if this test should be run by default or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDefault();
|
||||
public function isDefault(): bool;
|
||||
|
||||
/**
|
||||
* Returns true if this group has been run
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isCompleted();
|
||||
public function isCompleted(): bool;
|
||||
|
||||
/**
|
||||
* Mark this group as completed
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function markCompleted();
|
||||
public function markCompleted(): void;
|
||||
}
|
||||
|
@ -44,6 +44,8 @@ class ValidationResult
|
||||
private $list;
|
||||
/** @var string|null */
|
||||
private $fix;
|
||||
/** @var string|null */
|
||||
private $fixer;
|
||||
|
||||
/**
|
||||
* ValidationResult constructor.
|
||||
@ -226,6 +228,7 @@ class ValidationResult
|
||||
'statusText' => substr($this->getStatusText($resultStatus), 2, -2), // remove console colors
|
||||
'message' => $this->getMessage(),
|
||||
'fix' => Arr::wrap($resultFix),
|
||||
'fixer' => $this->getFixer(),
|
||||
'listDescription' => $this->getListDescription(),
|
||||
'list' => is_array($resultList) ? array_values($resultList) : [],
|
||||
];
|
||||
@ -249,4 +252,30 @@ class ValidationResult
|
||||
printf($format, " and $extra more...");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixer exists
|
||||
*/
|
||||
public function hasFixer(): bool
|
||||
{
|
||||
return $this->fixer !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null the class of the fixer
|
||||
*/
|
||||
public function getFixer(): ?string
|
||||
{
|
||||
return $this->fixer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set fixer, optionally denote if this is fixable
|
||||
*/
|
||||
public function setFixer(string $fixer, bool $fixable = true): ValidationResult
|
||||
{
|
||||
$this->fixer = $fixable ? $fixer : null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -25,39 +25,55 @@
|
||||
|
||||
namespace LibreNMS\Validations;
|
||||
|
||||
use LibreNMS\Interfaces\Validation;
|
||||
use LibreNMS\Interfaces\ValidationGroup;
|
||||
use LibreNMS\Validator;
|
||||
|
||||
abstract class BaseValidation implements ValidationGroup
|
||||
{
|
||||
/** @var bool */
|
||||
protected $completed = false;
|
||||
/** @var bool */
|
||||
protected static $RUN_BY_DEFAULT = true;
|
||||
/** @var string */
|
||||
protected $directory = null;
|
||||
/** @var string */
|
||||
protected $name = null;
|
||||
|
||||
public function validate(Validator $validator): void
|
||||
{
|
||||
if ($this->directory) {
|
||||
foreach (glob(__DIR__ . "/$this->directory/*.php") as $file) {
|
||||
$base = basename($file, '.php');
|
||||
$class = __NAMESPACE__ . "\\$this->directory\\$base";
|
||||
$validation = new $class;
|
||||
if ($validation instanceof Validation && $validation->enabled()) {
|
||||
$validator->result($validation->validate(), $this->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this test should be run by default or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDefault()
|
||||
public function isDefault(): bool
|
||||
{
|
||||
return static::$RUN_BY_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this group has been run
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isCompleted()
|
||||
public function isCompleted(): bool
|
||||
{
|
||||
return $this->completed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this group as completed
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function markCompleted()
|
||||
public function markCompleted(): void
|
||||
{
|
||||
$this->completed = true;
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ class Configuration extends BaseValidation
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
public function validate(Validator $validator)
|
||||
public function validate(Validator $validator): void
|
||||
{
|
||||
// Test transports
|
||||
if (Config::get('alerts.email.enable') == true) {
|
||||
|
@ -25,439 +25,33 @@
|
||||
|
||||
namespace LibreNMS\Validations;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonInterval;
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\DB\Eloquent;
|
||||
use LibreNMS\DB\Schema;
|
||||
use LibreNMS\ValidationResult;
|
||||
use LibreNMS\Validations\Database\CheckDatabaseServerVersion;
|
||||
use LibreNMS\Validations\Database\CheckDatabaseTableNamesCase;
|
||||
use LibreNMS\Validations\Database\CheckMysqlEngine;
|
||||
use LibreNMS\Validations\Database\CheckSqlServerTime;
|
||||
use LibreNMS\Validator;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class Database extends BaseValidation
|
||||
{
|
||||
const MYSQL_MIN_VERSION = '5.7.7';
|
||||
const MYSQL_MIN_VERSION_DATE = 'March, 2021';
|
||||
const MYSQL_RECOMMENDED_VERSION = '8.0';
|
||||
public const MYSQL_MIN_VERSION = '5.7.7';
|
||||
public const MYSQL_MIN_VERSION_DATE = 'March, 2021';
|
||||
public const MYSQL_RECOMMENDED_VERSION = '8.0';
|
||||
|
||||
const MARIADB_MIN_VERSION = '10.2.2';
|
||||
const MARIADB_MIN_VERSION_DATE = 'March, 2021';
|
||||
const MARIADB_RECOMMENDED_VERSION = '10.5';
|
||||
public const MARIADB_MIN_VERSION = '10.2.2';
|
||||
public const MARIADB_MIN_VERSION_DATE = 'March, 2021';
|
||||
public const MARIADB_RECOMMENDED_VERSION = '10.5';
|
||||
|
||||
public function validate(Validator $validator)
|
||||
{
|
||||
if (! Eloquent::isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->validateSystem($validator);
|
||||
|
||||
if ($this->checkSchemaVersion($validator)) {
|
||||
$this->checkSchema($validator);
|
||||
$this->checkCollation($validator);
|
||||
}
|
||||
}
|
||||
|
||||
public function validateSystem(Validator $validator)
|
||||
{
|
||||
$this->checkVersion($validator);
|
||||
$this->checkMode($validator);
|
||||
$this->checkTime($validator);
|
||||
$this->checkMysqlEngine($validator);
|
||||
}
|
||||
|
||||
private function checkSchemaVersion(Validator $validator): bool
|
||||
{
|
||||
$current = \LibreNMS\DB\Schema::getLegacySchema();
|
||||
$latest = 1000;
|
||||
|
||||
if ($current === 0 || $current === $latest) {
|
||||
// Using Laravel migrations
|
||||
if (! Schema::isCurrent()) {
|
||||
$validator->fail('Your database is out of date!', './lnms migrate');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$migrations = Schema::getUnexpectedMigrations();
|
||||
if ($migrations->isNotEmpty()) {
|
||||
$validator->warn('Your database schema has extra migrations (' . $migrations->implode(', ') .
|
||||
'). If you just switched to the stable release from the daily release, your database is in between releases and this will be resolved with the next release.');
|
||||
}
|
||||
} elseif ($current < $latest) {
|
||||
$validator->fail(
|
||||
"Your database schema ($current) is older than the latest ($latest).",
|
||||
'Manually run ./daily.sh, and check for any errors.'
|
||||
);
|
||||
|
||||
return false;
|
||||
} elseif ($current > $latest) {
|
||||
$validator->warn("Your database schema ($current) is newer than expected ($latest). If you just switched to the stable release from the daily release, your database is in between releases and this will be resolved with the next release.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function checkVersion(Validator $validator)
|
||||
{
|
||||
$version = \LibreNMS\Util\Version::get()->databaseServer();
|
||||
$version = explode('-', $version);
|
||||
|
||||
if (isset($version[1]) && $version[1] == 'MariaDB') {
|
||||
if (version_compare($version[0], self::MARIADB_MIN_VERSION, '<=')) {
|
||||
$validator->fail(
|
||||
'MariaDB version ' . self::MARIADB_MIN_VERSION . ' is the minimum supported version as of ' .
|
||||
self::MARIADB_MIN_VERSION_DATE . '.',
|
||||
'Update MariaDB to a supported version, ' . self::MARIADB_RECOMMENDED_VERSION . ' suggested.'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (version_compare($version[0], self::MYSQL_MIN_VERSION, '<=')) {
|
||||
$validator->fail(
|
||||
'MySQL version ' . self::MYSQL_MIN_VERSION . ' is the minimum supported version as of ' .
|
||||
self::MYSQL_MIN_VERSION_DATE . '.',
|
||||
'Update MySQL to a supported version, ' . self::MYSQL_RECOMMENDED_VERSION . ' suggested.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function checkTime(Validator $validator)
|
||||
{
|
||||
$raw_time = Eloquent::DB()->selectOne('SELECT NOW() as time')->time;
|
||||
$db_time = new Carbon($raw_time);
|
||||
$php_time = Carbon::now();
|
||||
|
||||
$diff = $db_time->diffAsCarbonInterval($php_time);
|
||||
|
||||
if ($diff->compare(CarbonInterval::minute(1)) > 0) {
|
||||
$message = "Time between this server and the mysql database is off\n";
|
||||
$message .= ' Mysql time ' . $db_time->toDateTimeString() . PHP_EOL;
|
||||
$message .= ' PHP time ' . $php_time->toDateTimeString() . PHP_EOL;
|
||||
|
||||
$validator->fail($message);
|
||||
}
|
||||
}
|
||||
|
||||
private function checkMode(Validator $validator)
|
||||
{
|
||||
// Test for lower case table name support
|
||||
$lc_mode = Eloquent::DB()->selectOne('SELECT @@global.lower_case_table_names as mode')->mode;
|
||||
if ($lc_mode != 0) {
|
||||
$validator->fail(
|
||||
'You have lower_case_table_names set to 1 or true in mysql config.',
|
||||
'Set lower_case_table_names=0 in your mysql config file in the [mysqld] section.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function checkMysqlEngine(Validator $validator)
|
||||
{
|
||||
$db = \config('database.connections.' . \config('database.default') . '.database');
|
||||
$query = "SELECT `TABLE_NAME` FROM information_schema.tables WHERE `TABLE_SCHEMA` = '$db' && `ENGINE` != 'InnoDB'";
|
||||
$tables = Eloquent::DB()->select($query);
|
||||
if (! empty($tables)) {
|
||||
$validator->result(
|
||||
ValidationResult::warn('Some tables are not using the recommended InnoDB engine, this may cause you issues.')
|
||||
->setList('Tables', array_column($tables, 'TABLE_NAME'))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function checkCollation(Validator $validator)
|
||||
{
|
||||
$db_name = Eloquent::DB()->selectOne('SELECT DATABASE() as name')->name;
|
||||
|
||||
// Test for correct character set and collation
|
||||
$db_collation_sql = "SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
|
||||
FROM information_schema.SCHEMATA S
|
||||
WHERE schema_name = '$db_name' AND
|
||||
( DEFAULT_CHARACTER_SET_NAME != 'utf8mb4' OR DEFAULT_COLLATION_NAME != 'utf8mb4_unicode_ci')";
|
||||
$collation = Eloquent::DB()->selectOne($db_collation_sql);
|
||||
if (empty($collation) !== true) {
|
||||
$validator->fail(
|
||||
"MySQL Database collation is wrong: $collation->DEFAULT_CHARACTER_SET_NAME $collation->DEFAULT_COLLATION_NAME",
|
||||
'Check https://community.librenms.org/t/new-default-database-charset-collation/14956 for info on how to fix.'
|
||||
);
|
||||
}
|
||||
|
||||
$table_collation_sql = "SELECT T.TABLE_NAME, C.CHARACTER_SET_NAME, C.COLLATION_NAME
|
||||
FROM information_schema.TABLES AS T, information_schema.COLLATION_CHARACTER_SET_APPLICABILITY AS C
|
||||
WHERE C.collation_name = T.table_collation AND T.table_schema = '$db_name' AND
|
||||
( C.CHARACTER_SET_NAME != 'utf8mb4' OR C.COLLATION_NAME != 'utf8mb4_unicode_ci' );";
|
||||
$collation_tables = Eloquent::DB()->select($table_collation_sql);
|
||||
if (empty($collation_tables) !== true) {
|
||||
$result = ValidationResult::fail('MySQL tables collation is wrong: ')
|
||||
->setFix('Check https://community.librenms.org/t/new-default-database-charset-collation/14956 for info on how to fix.')
|
||||
->setList('Tables', array_map(function ($row) {
|
||||
return "$row->TABLE_NAME $row->CHARACTER_SET_NAME $row->COLLATION_NAME";
|
||||
}, $collation_tables));
|
||||
$validator->result($result);
|
||||
}
|
||||
|
||||
$column_collation_sql = "SELECT TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAME
|
||||
FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '$db_name' AND
|
||||
( CHARACTER_SET_NAME != 'utf8mb4' OR COLLATION_NAME != 'utf8mb4_unicode_ci' );";
|
||||
$collation_columns = Eloquent::DB()->select($column_collation_sql);
|
||||
if (empty($collation_columns) !== true) {
|
||||
$result = ValidationResult::fail('MySQL column collation is wrong: ')
|
||||
->setFix('Check https://community.librenms.org/t/new-default-database-charset-collation/14956 for info on how to fix.')
|
||||
->setList('Columns', array_map(function ($row) {
|
||||
return "$row->TABLE_NAME: $row->COLUMN_NAME $row->CHARACTER_SET_NAME $row->COLLATION_NAME";
|
||||
}, $collation_columns));
|
||||
$validator->result($result);
|
||||
}
|
||||
}
|
||||
|
||||
private function checkSchema(Validator $validator)
|
||||
{
|
||||
$schema_file = Config::get('install_dir') . '/misc/db_schema.yaml';
|
||||
|
||||
if (! is_file($schema_file)) {
|
||||
$validator->warn("We haven't detected the db_schema.yaml file");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$master_schema = Yaml::parse(file_get_contents($schema_file));
|
||||
$current_schema = Schema::dump();
|
||||
$schema_update = [];
|
||||
|
||||
foreach ((array) $master_schema as $table => $data) {
|
||||
if (empty($current_schema[$table])) {
|
||||
$validator->fail("Database: missing table ($table)");
|
||||
$schema_update[] = $this->addTableSql($table, $data);
|
||||
} else {
|
||||
$current_columns = array_reduce($current_schema[$table]['Columns'], function ($array, $item) {
|
||||
$array[$item['Field']] = $item;
|
||||
|
||||
return $array;
|
||||
}, []);
|
||||
|
||||
foreach ($data['Columns'] as $index => $cdata) {
|
||||
$column = $cdata['Field'];
|
||||
|
||||
// MySQL 8 fix, remove DEFAULT_GENERATED from timestamp extra columns
|
||||
if ($cdata['Type'] == 'timestamp') {
|
||||
$current_columns[$column]['Extra'] = preg_replace('/DEFAULT_GENERATED[ ]*/', '', $current_columns[$column]['Extra']);
|
||||
}
|
||||
|
||||
if (empty($current_columns[$column])) {
|
||||
$validator->fail("Database: missing column ($table/$column)");
|
||||
$primary = false;
|
||||
if ($data['Indexes']['PRIMARY']['Columns'] == [$column]) {
|
||||
// include the primary index with the add statement
|
||||
unset($data['Indexes']['PRIMARY']);
|
||||
$primary = true;
|
||||
}
|
||||
$schema_update[] = $this->addColumnSql($table, $cdata, isset($data['Columns'][$index - 1]) ? $data['Columns'][$index - 1]['Field'] : null, $primary);
|
||||
} elseif ($cdata !== $current_columns[$column]) {
|
||||
$validator->fail("Database: incorrect column ($table/$column)");
|
||||
$schema_update[] = $this->updateTableSql($table, $column, $cdata);
|
||||
}
|
||||
|
||||
unset($current_columns[$column]); // remove checked columns
|
||||
}
|
||||
|
||||
foreach ($current_columns as $column => $_unused) {
|
||||
$validator->fail("Database: extra column ($table/$column)");
|
||||
$schema_update[] = $this->dropColumnSql($table, $column);
|
||||
}
|
||||
|
||||
$index_changes = [];
|
||||
if (isset($data['Indexes'])) {
|
||||
foreach ($data['Indexes'] as $name => $index) {
|
||||
if (empty($current_schema[$table]['Indexes'][$name])) {
|
||||
$validator->fail("Database: missing index ($table/$name)");
|
||||
$index_changes[] = $this->addIndexSql($table, $index);
|
||||
} elseif ($index != $current_schema[$table]['Indexes'][$name]) {
|
||||
$validator->fail("Database: incorrect index ($table/$name)");
|
||||
$index_changes[] = $this->updateIndexSql($table, $name, $index);
|
||||
}
|
||||
|
||||
unset($current_schema[$table]['Indexes'][$name]);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($current_schema[$table]['Indexes'])) {
|
||||
foreach ($current_schema[$table]['Indexes'] as $name => $_unused) {
|
||||
$validator->fail("Database: extra index ($table/$name)");
|
||||
$schema_update[] = $this->dropIndexSql($table, $name);
|
||||
}
|
||||
}
|
||||
$schema_update = array_merge($schema_update, $index_changes); // drop before create/update
|
||||
|
||||
$constraint_changes = [];
|
||||
if (isset($data['Constraints'])) {
|
||||
foreach ($data['Constraints'] as $name => $constraint) {
|
||||
if (empty($current_schema[$table]['Constraints'][$name])) {
|
||||
$validator->fail("Database: missing constraint ($table/$name)");
|
||||
$constraint_changes[] = $this->addConstraintSql($table, $constraint);
|
||||
} elseif ($constraint != $current_schema[$table]['Constraints'][$name]) {
|
||||
$validator->fail("Database: incorrect constraint ($table/$name)");
|
||||
$constraint_changes[] = $this->dropConstraintSql($table, $name);
|
||||
$constraint_changes[] = $this->addConstraintSql($table, $constraint);
|
||||
}
|
||||
|
||||
unset($current_schema[$table]['Constraints'][$name]);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($current_schema[$table]['Constraints'])) {
|
||||
foreach ($current_schema[$table]['Constraints'] as $name => $_unused) {
|
||||
$validator->fail("Database: extra constraint ($table/$name)");
|
||||
$schema_update[] = $this->dropConstraintSql($table, $name);
|
||||
}
|
||||
}
|
||||
$schema_update = array_merge($schema_update, $constraint_changes); // drop before create/update
|
||||
}
|
||||
|
||||
unset($current_schema[$table]); // remove checked tables
|
||||
}
|
||||
|
||||
foreach ($current_schema as $table => $data) {
|
||||
$validator->fail("Database: extra table ($table)");
|
||||
$schema_update[] = $this->dropTableSql($table);
|
||||
}
|
||||
|
||||
// set utc timezone if timestamp issues
|
||||
if (preg_grep('/\d{4}-\d\d-\d\d \d\d:\d\d:\d\d/', $schema_update)) {
|
||||
array_unshift($schema_update, "SET TIME_ZONE='+00:00';");
|
||||
}
|
||||
|
||||
if (empty($schema_update)) {
|
||||
$validator->ok('Database schema correct');
|
||||
} else {
|
||||
$result = ValidationResult::fail('We have detected that your database schema may be wrong')
|
||||
->setFix('Run the following SQL statements to fix it')
|
||||
->setList('SQL Statements', $schema_update);
|
||||
$validator->result($result);
|
||||
}
|
||||
}
|
||||
|
||||
private function addTableSql($table, $table_schema)
|
||||
{
|
||||
$columns = array_map([$this, 'columnToSql'], $table_schema['Columns']);
|
||||
$indexes = array_map([$this, 'indexToSql'], isset($table_schema['Indexes']) ? $table_schema['Indexes'] : []);
|
||||
|
||||
$def = implode(', ', array_merge(array_values((array) $columns), array_values((array) $indexes)));
|
||||
|
||||
return "CREATE TABLE `$table` ($def);";
|
||||
}
|
||||
|
||||
private function addColumnSql($table, $schema, $previous_column, $primary = false)
|
||||
{
|
||||
$sql = "ALTER TABLE `$table` ADD " . $this->columnToSql($schema);
|
||||
if ($primary) {
|
||||
$sql .= ' PRIMARY KEY';
|
||||
}
|
||||
if (empty($previous_column)) {
|
||||
$sql .= ' FIRST';
|
||||
} else {
|
||||
$sql .= " AFTER `$previous_column`";
|
||||
}
|
||||
|
||||
return $sql . ';';
|
||||
}
|
||||
|
||||
private function updateTableSql($table, $column, $column_schema)
|
||||
{
|
||||
return "ALTER TABLE `$table` CHANGE `$column` " . $this->columnToSql($column_schema) . ';';
|
||||
}
|
||||
|
||||
private function dropColumnSql($table, $column)
|
||||
{
|
||||
return "ALTER TABLE `$table` DROP `$column`;";
|
||||
}
|
||||
|
||||
private function addIndexSql($table, $index_schema)
|
||||
{
|
||||
return "ALTER TABLE `$table` ADD " . $this->indexToSql($index_schema) . ';';
|
||||
}
|
||||
|
||||
private function updateIndexSql($table, $name, $index_schema)
|
||||
{
|
||||
return "ALTER TABLE `$table` DROP INDEX `$name`, " . $this->indexToSql($index_schema) . ';';
|
||||
}
|
||||
|
||||
private function dropIndexSql($table, $name)
|
||||
{
|
||||
return "ALTER TABLE `$table` DROP INDEX `$name`;";
|
||||
}
|
||||
|
||||
private function dropTableSql($table)
|
||||
{
|
||||
return "DROP TABLE `$table`;";
|
||||
}
|
||||
protected $directory = 'Database';
|
||||
protected $name = 'database';
|
||||
|
||||
/**
|
||||
* Generate an SQL segment to create the column based on data from Schema::dump()
|
||||
*
|
||||
* @param array $column_data The array of data for the column
|
||||
* @return string sql fragment, for example: "`ix_id` int(10) unsigned NOT NULL"
|
||||
* Tests used by the installer to validate that SQL server doesn't have any known issues (before migrations)
|
||||
*/
|
||||
private function columnToSql($column_data)
|
||||
public function validateSystem(Validator $validator): void
|
||||
{
|
||||
$segments = ["`${column_data['Field']}`", $column_data['Type']];
|
||||
|
||||
$segments[] = $column_data['Null'] ? 'NULL' : 'NOT NULL';
|
||||
|
||||
if (isset($column_data['Default'])) {
|
||||
if ($column_data['Default'] === 'CURRENT_TIMESTAMP') {
|
||||
$segments[] = 'DEFAULT CURRENT_TIMESTAMP';
|
||||
} elseif ($column_data['Default'] == 'NULL') {
|
||||
$segments[] = 'DEFAULT NULL';
|
||||
} else {
|
||||
$segments[] = "DEFAULT '${column_data['Default']}'";
|
||||
}
|
||||
}
|
||||
|
||||
if ($column_data['Extra'] == 'on update current_timestamp()') {
|
||||
$segments[] = 'on update CURRENT_TIMESTAMP';
|
||||
} else {
|
||||
$segments[] = $column_data['Extra'];
|
||||
}
|
||||
|
||||
return implode(' ', $segments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an SQL segment to create the index based on data from Schema::dump()
|
||||
*
|
||||
* @param array $index_data The array of data for the index
|
||||
* @return string sql fragment, for example: "PRIMARY KEY (`device_id`)"
|
||||
*/
|
||||
private function indexToSql($index_data)
|
||||
{
|
||||
if ($index_data['Name'] == 'PRIMARY') {
|
||||
$index = 'PRIMARY KEY (%s)';
|
||||
} elseif ($index_data['Unique']) {
|
||||
$index = "UNIQUE `{$index_data['Name']}` (%s)";
|
||||
} else {
|
||||
$index = "INDEX `{$index_data['Name']}` (%s)";
|
||||
}
|
||||
|
||||
$columns = implode(',', array_map(function ($col) {
|
||||
return "`$col`";
|
||||
}, $index_data['Columns']));
|
||||
|
||||
return sprintf($index, $columns);
|
||||
}
|
||||
|
||||
private function addConstraintSql($table, $constraint)
|
||||
{
|
||||
$sql = "ALTER TABLE `$table` ADD CONSTRAINT `{$constraint['name']}` FOREIGN KEY (`{$constraint['foreign_key']}`) ";
|
||||
$sql .= " REFERENCES `{$constraint['table']}` (`{$constraint['key']}`)";
|
||||
if (! empty($constraint['extra'])) {
|
||||
$sql .= ' ' . $constraint['extra'];
|
||||
}
|
||||
$sql .= ';';
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
private function dropConstraintSql($table, $name)
|
||||
{
|
||||
return "ALTER TABLE `$table` DROP FOREIGN KEY `$name`;";
|
||||
$validator->result((new CheckDatabaseServerVersion)->validate(), $this->name);
|
||||
$validator->result((new CheckMysqlEngine)->validate(), $this->name);
|
||||
$validator->result((new CheckSqlServerTime)->validate(), $this->name);
|
||||
$validator->result((new CheckDatabaseTableNamesCase)->validate(), $this->name);
|
||||
}
|
||||
}
|
||||
|
95
LibreNMS/Validations/Database/CheckDatabaseSchemaVersion.php
Normal file
95
LibreNMS/Validations/Database/CheckDatabaseSchemaVersion.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/*
|
||||
* CheckSchemaVersion.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 2022 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Validations\Database;
|
||||
|
||||
use LibreNMS\DB\Eloquent;
|
||||
use LibreNMS\DB\Schema;
|
||||
use LibreNMS\Interfaces\Validation;
|
||||
use LibreNMS\Interfaces\ValidationFixer;
|
||||
use LibreNMS\ValidationResult;
|
||||
|
||||
class CheckDatabaseSchemaVersion implements Validation, ValidationFixer
|
||||
{
|
||||
/** @var bool|null */
|
||||
private static $current = null;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function validate(): ValidationResult
|
||||
{
|
||||
self::$current = false;
|
||||
$current = \LibreNMS\DB\Schema::getLegacySchema();
|
||||
$latest = 1000;
|
||||
|
||||
if ($current === 0 || $current === $latest) {
|
||||
// Using Laravel migrations
|
||||
if (! Schema::isCurrent()) {
|
||||
return ValidationResult::fail(trans('validation.validations.database.CheckSchemaVersion.fail_outdated'), './lnms migrate')
|
||||
->setFixer(__CLASS__);
|
||||
}
|
||||
|
||||
$migrations = Schema::getUnexpectedMigrations();
|
||||
if ($migrations->isNotEmpty()) {
|
||||
return ValidationResult::warn(trans('validation.validations.database.CheckSchemaVersion.warn_extra_migrations', ['migrations' => $migrations->implode(', ')]));
|
||||
}
|
||||
} elseif ($current < $latest) {
|
||||
return ValidationResult::fail(
|
||||
trans('validation.validations.database.CheckSchemaVersion.fail_legacy_outdated', ['current' => $current, 'latest' => $latest]),
|
||||
trans('validation.validations.database.CheckSchemaVersion.fix_legacy_outdated'),
|
||||
);
|
||||
} else {
|
||||
// latest > current
|
||||
return ValidationResult::warn(trans('validation.validations.database.CheckSchemaVersion.warn_legacy_newer'));
|
||||
}
|
||||
|
||||
self::$current = true;
|
||||
|
||||
return ValidationResult::ok(trans('validation.validations.database.CheckSchemaVersion.ok'));
|
||||
}
|
||||
|
||||
public static function isCurrent(): bool
|
||||
{
|
||||
if (self::$current === null) {
|
||||
(new static)->validate();
|
||||
}
|
||||
|
||||
return self::$current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function enabled(): bool
|
||||
{
|
||||
return Eloquent::isConnected();
|
||||
}
|
||||
|
||||
public function fix(): bool
|
||||
{
|
||||
return \Artisan::call('migrate', ['--force' => true]) === 0;
|
||||
}
|
||||
}
|
70
LibreNMS/Validations/Database/CheckDatabaseServerVersion.php
Normal file
70
LibreNMS/Validations/Database/CheckDatabaseServerVersion.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/*
|
||||
* CheckDatabaseServerVersion.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 2022 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Validations\Database;
|
||||
|
||||
use LibreNMS\DB\Eloquent;
|
||||
use LibreNMS\Interfaces\Validation;
|
||||
use LibreNMS\Util\Version;
|
||||
use LibreNMS\ValidationResult;
|
||||
use LibreNMS\Validations\Database;
|
||||
|
||||
class CheckDatabaseServerVersion implements Validation
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function validate(): ValidationResult
|
||||
{
|
||||
$version = Version::get()->databaseServer();
|
||||
$version = explode('-', $version);
|
||||
|
||||
if (isset($version[1]) && $version[1] == 'MariaDB') {
|
||||
if (version_compare($version[0], Database::MARIADB_MIN_VERSION, '<=')) {
|
||||
return ValidationResult::fail(
|
||||
trans('validation.validations.database.CheckDatabaseServerVersion.fail', ['server' => 'MariaDB', 'min' => Database::MARIADB_MIN_VERSION, 'date' => Database::MARIADB_MIN_VERSION_DATE]),
|
||||
trans('validation.validations.database.CheckDatabaseServerVersion.fix', ['server' => 'MariaDB', 'suggested' => Database::MARIADB_RECOMMENDED_VERSION]),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (version_compare($version[0], Database::MYSQL_MIN_VERSION, '<=')) {
|
||||
return ValidationResult::fail(
|
||||
trans('validation.validations.database.CheckDatabaseServerVersion.fail', ['server' => 'MySQL', 'min' => Database::MYSQL_MIN_VERSION, 'date' => Database::MYSQL_MIN_VERSION_DATE]),
|
||||
trans('validation.validations.database.CheckDatabaseServerVersion.fix', ['server' => 'MySQL', 'suggested' => Database::MYSQL_RECOMMENDED_VERSION]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return ValidationResult::ok(trans('validation.validations.database.CheckDatabaseServerVersion.ok'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function enabled(): bool
|
||||
{
|
||||
return Eloquent::isConnected();
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/*
|
||||
* CheckDatabaseTableNamesCase.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 2022 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Validations\Database;
|
||||
|
||||
use DB;
|
||||
use LibreNMS\DB\Eloquent;
|
||||
use LibreNMS\Interfaces\Validation;
|
||||
use LibreNMS\ValidationResult;
|
||||
|
||||
class CheckDatabaseTableNamesCase implements Validation
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function validate(): ValidationResult
|
||||
{
|
||||
// Test for lower case table name support
|
||||
$lc_mode = DB::selectOne('SELECT @@global.lower_case_table_names as mode')->mode;
|
||||
if ($lc_mode != 0) {
|
||||
ValidationResult::fail(
|
||||
trans('validation.validations.database.CheckDatabaseTableNamesCase.fail'),
|
||||
trans('validation.validations.database.CheckDatabaseTableNamesCase.fix')
|
||||
);
|
||||
}
|
||||
|
||||
return ValidationResult::ok(trans('validation.validations.database.CheckDatabaseTableNamesCase.ok'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function enabled(): bool
|
||||
{
|
||||
return Eloquent::isConnected();
|
||||
}
|
||||
}
|
95
LibreNMS/Validations/Database/CheckMysqlEngine.php
Normal file
95
LibreNMS/Validations/Database/CheckMysqlEngine.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/*
|
||||
* CheckMysqlEngine.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 2022 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Validations\Database;
|
||||
|
||||
use DB;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Collection;
|
||||
use LibreNMS\DB\Eloquent;
|
||||
use LibreNMS\Interfaces\Validation;
|
||||
use LibreNMS\Interfaces\ValidationFixer;
|
||||
use LibreNMS\ValidationResult;
|
||||
|
||||
class CheckMysqlEngine implements Validation, ValidationFixer
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function validate(): ValidationResult
|
||||
{
|
||||
$tables = $this->findNonInnodbTables();
|
||||
|
||||
if ($tables->isNotEmpty()) {
|
||||
return ValidationResult::warn(trans('validation.validations.database.CheckMysqlEngine.fail'))
|
||||
->setFixer(__CLASS__)
|
||||
->setList(trans('validation.validations.database.CheckMysqlEngine.tables'), $tables->all());
|
||||
}
|
||||
|
||||
return ValidationResult::ok(trans('validation.validations.database.CheckMysqlEngine.ok'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function enabled(): bool
|
||||
{
|
||||
return Eloquent::isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function fix(): bool
|
||||
{
|
||||
try {
|
||||
$db = $this->databaseName();
|
||||
$tables = $this->findNonInnodbTables();
|
||||
|
||||
foreach ($tables as $table) {
|
||||
DB::statement("ALTER TABLE $db.$table ENGINE=InnoDB;");
|
||||
}
|
||||
} catch (QueryException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function databaseName(): string
|
||||
{
|
||||
return \config('database.connections.' . \config('database.default') . '.database');
|
||||
}
|
||||
|
||||
private function findNonInnodbTables(): Collection
|
||||
{
|
||||
$db = $this->databaseName();
|
||||
|
||||
return DB::table('information_schema.tables')
|
||||
->where('TABLE_SCHEMA', $db)
|
||||
->where('ENGINE', '!=', 'InnoDB')
|
||||
->pluck('TABLE_NAME');
|
||||
}
|
||||
}
|
100
LibreNMS/Validations/Database/CheckSchemaCollation.php
Normal file
100
LibreNMS/Validations/Database/CheckSchemaCollation.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/*
|
||||
* CheckSchemaCollation.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 2022 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Validations\Database;
|
||||
|
||||
use LibreNMS\DB\Eloquent;
|
||||
use LibreNMS\Interfaces\Validation;
|
||||
use LibreNMS\Interfaces\ValidationFixer;
|
||||
use LibreNMS\ValidationResult;
|
||||
|
||||
class CheckSchemaCollation implements Validation, ValidationFixer
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function validate(): ValidationResult
|
||||
{
|
||||
$db_name = Eloquent::DB()->selectOne('SELECT DATABASE() as name')->name;
|
||||
|
||||
// Test for correct character set and collation
|
||||
$db_collation_sql = "SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
|
||||
FROM information_schema.SCHEMATA S
|
||||
WHERE schema_name = '$db_name' AND
|
||||
( DEFAULT_CHARACTER_SET_NAME != 'utf8mb4' OR DEFAULT_COLLATION_NAME != 'utf8mb4_unicode_ci')";
|
||||
$collation = Eloquent::DB()->selectOne($db_collation_sql);
|
||||
if (empty($collation) !== true) {
|
||||
return ValidationResult::fail(
|
||||
"MySQL Database collation is wrong: $collation->DEFAULT_CHARACTER_SET_NAME $collation->DEFAULT_COLLATION_NAME",
|
||||
'Check https://community.librenms.org/t/new-default-database-charset-collation/14956 for info on how to fix.'
|
||||
)->setFixer(__CLASS__);
|
||||
}
|
||||
|
||||
$table_collation_sql = "SELECT T.TABLE_NAME, C.CHARACTER_SET_NAME, C.COLLATION_NAME
|
||||
FROM information_schema.TABLES AS T, information_schema.COLLATION_CHARACTER_SET_APPLICABILITY AS C
|
||||
WHERE C.collation_name = T.table_collation AND T.table_schema = '$db_name' AND
|
||||
( C.CHARACTER_SET_NAME != 'utf8mb4' OR C.COLLATION_NAME != 'utf8mb4_unicode_ci' );";
|
||||
$collation_tables = Eloquent::DB()->select($table_collation_sql);
|
||||
if (empty($collation_tables) !== true) {
|
||||
return ValidationResult::fail('MySQL tables collation is wrong: ')
|
||||
->setFix('Check https://community.librenms.org/t/new-default-database-charset-collation/14956 for info on how to fix.')
|
||||
->setFixer(__CLASS__)
|
||||
->setList('Tables', array_map(function ($row) {
|
||||
return "$row->TABLE_NAME $row->CHARACTER_SET_NAME $row->COLLATION_NAME";
|
||||
}, $collation_tables));
|
||||
}
|
||||
|
||||
$column_collation_sql = "SELECT TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAME
|
||||
FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '$db_name' AND
|
||||
( CHARACTER_SET_NAME != 'utf8mb4' OR COLLATION_NAME != 'utf8mb4_unicode_ci' );";
|
||||
$collation_columns = Eloquent::DB()->select($column_collation_sql);
|
||||
if (empty($collation_columns) !== true) {
|
||||
return ValidationResult::fail('MySQL column collation is wrong: ')
|
||||
->setFix('Check https://community.librenms.org/t/new-default-database-charset-collation/14956 for info on how to fix.')
|
||||
->setFixer(__CLASS__)
|
||||
->setList('Columns', array_map(function ($row) {
|
||||
return "$row->TABLE_NAME: $row->COLUMN_NAME $row->CHARACTER_SET_NAME $row->COLLATION_NAME";
|
||||
}, $collation_columns));
|
||||
}
|
||||
|
||||
return ValidationResult::ok('');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function enabled(): bool
|
||||
{
|
||||
return Eloquent::isConnected() && CheckDatabaseSchemaVersion::isCurrent();
|
||||
}
|
||||
|
||||
public function fix(): bool
|
||||
{
|
||||
\DB::table('migrations')->where('migration', '2021_02_09_122930_migrate_to_utf8mb4')->delete();
|
||||
$res = \Artisan::call('migrate', ['--force' => true]);
|
||||
|
||||
return $res === 0;
|
||||
}
|
||||
}
|
329
LibreNMS/Validations/Database/CheckSchemaStructure.php
Normal file
329
LibreNMS/Validations/Database/CheckSchemaStructure.php
Normal file
@ -0,0 +1,329 @@
|
||||
<?php
|
||||
/*
|
||||
* CheckSchemaStructure.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 2022 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Validations\Database;
|
||||
|
||||
use DB;
|
||||
use Illuminate\Database\QueryException;
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\DB\Eloquent;
|
||||
use LibreNMS\DB\Schema;
|
||||
use LibreNMS\Interfaces\Validation;
|
||||
use LibreNMS\Interfaces\ValidationFixer;
|
||||
use LibreNMS\ValidationResult;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class CheckSchemaStructure implements Validation, ValidationFixer
|
||||
{
|
||||
/** @var array */
|
||||
private $descriptions = [];
|
||||
/** @var array */
|
||||
private $schema_update = [];
|
||||
/** @var string */
|
||||
private $schema_file;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->schema_file = Config::get('install_dir') . '/misc/db_schema.yaml';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function validate(): ValidationResult
|
||||
{
|
||||
if (! is_file($this->schema_file)) {
|
||||
return ValidationResult::warn("We haven't detected the db_schema.yaml file");
|
||||
}
|
||||
|
||||
$this->checkSchema();
|
||||
|
||||
if (empty($this->schema_update)) {
|
||||
return ValidationResult::ok('Database schema correct');
|
||||
}
|
||||
|
||||
return ValidationResult::fail("We have detected that your database schema may be wrong\n" . implode("\n", $this->descriptions))
|
||||
->setFix('Run the following SQL statements to fix it')
|
||||
->setFixer(__CLASS__)
|
||||
->setList('SQL Statements', $this->schema_update);
|
||||
}
|
||||
|
||||
public function fix(): bool
|
||||
{
|
||||
try {
|
||||
$this->checkSchema();
|
||||
|
||||
foreach ($this->schema_update as $query) {
|
||||
DB::statement($query);
|
||||
}
|
||||
} catch (QueryException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function enabled(): bool
|
||||
{
|
||||
return Eloquent::isConnected() && CheckDatabaseSchemaVersion::isCurrent();
|
||||
}
|
||||
|
||||
private function checkSchema(): void
|
||||
{
|
||||
$master_schema = Yaml::parse(file_get_contents($this->schema_file));
|
||||
$current_schema = Schema::dump();
|
||||
|
||||
foreach ((array) $master_schema as $table => $data) {
|
||||
if (empty($current_schema[$table])) {
|
||||
$this->descriptions[] = "Database: missing table ($table)";
|
||||
$this->schema_update[] = $this->addTableSql($table, $data);
|
||||
} else {
|
||||
$current_columns = array_reduce($current_schema[$table]['Columns'], function ($array, $item) {
|
||||
$array[$item['Field']] = $item;
|
||||
|
||||
return $array;
|
||||
}, []);
|
||||
|
||||
foreach ($data['Columns'] as $index => $cdata) {
|
||||
$column = $cdata['Field'];
|
||||
|
||||
// MySQL 8 fix, remove DEFAULT_GENERATED from timestamp extra columns
|
||||
if ($cdata['Type'] == 'timestamp') {
|
||||
$current_columns[$column]['Extra'] = preg_replace('/DEFAULT_GENERATED */', '', $current_columns[$column]['Extra']);
|
||||
}
|
||||
|
||||
if (empty($current_columns[$column])) {
|
||||
$this->descriptions[] = "Database: missing column ($table/$column)";
|
||||
$primary = false;
|
||||
if ($data['Indexes']['PRIMARY']['Columns'] == [$column]) {
|
||||
// include the primary index with the add statement
|
||||
unset($data['Indexes']['PRIMARY']);
|
||||
$primary = true;
|
||||
}
|
||||
$this->schema_update[] = $this->addColumnSql($table, $cdata, isset($data['Columns'][$index - 1]) ? $data['Columns'][$index - 1]['Field'] : null, $primary);
|
||||
} elseif ($cdata !== $current_columns[$column]) {
|
||||
$this->descriptions[] = "Database: incorrect column ($table/$column)";
|
||||
$this->schema_update[] = $this->updateTableSql($table, $column, $cdata);
|
||||
}
|
||||
|
||||
unset($current_columns[$column]); // remove checked columns
|
||||
}
|
||||
|
||||
foreach ($current_columns as $column => $_unused) {
|
||||
$this->descriptions[] = "Database: extra column ($table/$column)";
|
||||
$this->schema_update[] = $this->dropColumnSql($table, $column);
|
||||
}
|
||||
|
||||
$index_changes = [];
|
||||
if (isset($data['Indexes'])) {
|
||||
foreach ($data['Indexes'] as $name => $index) {
|
||||
if (empty($current_schema[$table]['Indexes'][$name])) {
|
||||
$this->descriptions[] = "Database: missing index ($table/$name)";
|
||||
$index_changes[] = $this->addIndexSql($table, $index);
|
||||
} elseif ($index != $current_schema[$table]['Indexes'][$name]) {
|
||||
$this->descriptions[] = "Database: incorrect index ($table/$name)";
|
||||
$index_changes[] = $this->updateIndexSql($table, $name, $index);
|
||||
}
|
||||
|
||||
unset($current_schema[$table]['Indexes'][$name]);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($current_schema[$table]['Indexes'])) {
|
||||
foreach ($current_schema[$table]['Indexes'] as $name => $_unused) {
|
||||
$this->descriptions[] = "Database: extra index ($table/$name)";
|
||||
$this->schema_update[] = $this->dropIndexSql($table, $name);
|
||||
}
|
||||
}
|
||||
$this->schema_update = array_merge($this->schema_update, $index_changes); // drop before create/update
|
||||
|
||||
$constraint_changes = [];
|
||||
if (isset($data['Constraints'])) {
|
||||
foreach ($data['Constraints'] as $name => $constraint) {
|
||||
if (empty($current_schema[$table]['Constraints'][$name])) {
|
||||
$this->descriptions[] = "Database: missing constraint ($table/$name)";
|
||||
$constraint_changes[] = $this->addConstraintSql($table, $constraint);
|
||||
} elseif ($constraint != $current_schema[$table]['Constraints'][$name]) {
|
||||
$this->descriptions[] = "Database: incorrect constraint ($table/$name)";
|
||||
$constraint_changes[] = $this->dropConstraintSql($table, $name);
|
||||
$constraint_changes[] = $this->addConstraintSql($table, $constraint);
|
||||
}
|
||||
|
||||
unset($current_schema[$table]['Constraints'][$name]);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($current_schema[$table]['Constraints'])) {
|
||||
foreach ($current_schema[$table]['Constraints'] as $name => $_unused) {
|
||||
$this->descriptions[] = "Database: extra constraint ($table/$name)";
|
||||
$this->schema_update[] = $this->dropConstraintSql($table, $name);
|
||||
}
|
||||
}
|
||||
$this->schema_update = array_merge($this->schema_update, $constraint_changes); // drop before create/update
|
||||
}
|
||||
|
||||
unset($current_schema[$table]); // remove checked tables
|
||||
}
|
||||
|
||||
foreach ($current_schema as $table => $data) {
|
||||
$this->descriptions[] = "Database: extra table ($table)";
|
||||
$this->schema_update[] = $this->dropTableSql($table);
|
||||
}
|
||||
|
||||
// set utc timezone if timestamp issues
|
||||
if (preg_grep('/\d{4}-\d\d-\d\d \d\d:\d\d:\d\d/', $this->schema_update)) {
|
||||
array_unshift($this->schema_update, "SET TIME_ZONE='+00:00';");
|
||||
}
|
||||
}
|
||||
|
||||
private function addTableSql(string $table, array $table_schema): string
|
||||
{
|
||||
$columns = array_map([$this, 'columnToSql'], $table_schema['Columns']);
|
||||
$indexes = array_map([$this, 'indexToSql'], $table_schema['Indexes'] ?? []);
|
||||
|
||||
$def = implode(', ', array_merge(array_values($columns), array_values($indexes)));
|
||||
|
||||
return "CREATE TABLE `$table` ($def);";
|
||||
}
|
||||
|
||||
private function addColumnSql(string $table, array $schema, ?string $previous_column, bool $primary = false): string
|
||||
{
|
||||
$sql = "ALTER TABLE `$table` ADD " . $this->columnToSql($schema);
|
||||
if ($primary) {
|
||||
$sql .= ' PRIMARY KEY';
|
||||
}
|
||||
if (empty($previous_column)) {
|
||||
$sql .= ' FIRST';
|
||||
} else {
|
||||
$sql .= " AFTER `$previous_column`";
|
||||
}
|
||||
|
||||
return $sql . ';';
|
||||
}
|
||||
|
||||
private function updateTableSql(string $table, string $column, array $column_schema): string
|
||||
{
|
||||
return "ALTER TABLE `$table` CHANGE `$column` " . $this->columnToSql($column_schema) . ';';
|
||||
}
|
||||
|
||||
private function dropColumnSql(string $table, string $column): string
|
||||
{
|
||||
return "ALTER TABLE `$table` DROP `$column`;";
|
||||
}
|
||||
|
||||
private function addIndexSql(string $table, array $index_schema): string
|
||||
{
|
||||
return "ALTER TABLE `$table` ADD " . $this->indexToSql($index_schema) . ';';
|
||||
}
|
||||
|
||||
private function updateIndexSql(string $table, string $name, array $index_schema): string
|
||||
{
|
||||
return "ALTER TABLE `$table` DROP INDEX `$name`, " . $this->indexToSql($index_schema) . ';';
|
||||
}
|
||||
|
||||
private function dropIndexSql(string $table, string $name): string
|
||||
{
|
||||
return "ALTER TABLE `$table` DROP INDEX `$name`;";
|
||||
}
|
||||
|
||||
private function dropTableSql(string $table): string
|
||||
{
|
||||
return "DROP TABLE `$table`;";
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an SQL segment to create the column based on data from Schema::dump()
|
||||
*
|
||||
* @param array $column_data The array of data for the column
|
||||
* @return string sql fragment, for example: "`ix_id` int(10) unsigned NOT NULL"
|
||||
*/
|
||||
private function columnToSql(array $column_data): string
|
||||
{
|
||||
$segments = ["`${column_data['Field']}`", $column_data['Type']];
|
||||
|
||||
$segments[] = $column_data['Null'] ? 'NULL' : 'NOT NULL';
|
||||
|
||||
if (isset($column_data['Default'])) {
|
||||
if ($column_data['Default'] === 'CURRENT_TIMESTAMP') {
|
||||
$segments[] = 'DEFAULT CURRENT_TIMESTAMP';
|
||||
} elseif ($column_data['Default'] == 'NULL') {
|
||||
$segments[] = 'DEFAULT NULL';
|
||||
} else {
|
||||
$segments[] = "DEFAULT '${column_data['Default']}'";
|
||||
}
|
||||
}
|
||||
|
||||
if ($column_data['Extra'] == 'on update current_timestamp()') {
|
||||
$segments[] = 'on update CURRENT_TIMESTAMP';
|
||||
} else {
|
||||
$segments[] = $column_data['Extra'];
|
||||
}
|
||||
|
||||
return implode(' ', $segments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an SQL segment to create the index based on data from Schema::dump()
|
||||
*
|
||||
* @param array $index_data The array of data for the index
|
||||
* @return string sql fragment, for example: "PRIMARY KEY (`device_id`)"
|
||||
*/
|
||||
private function indexToSql(array $index_data): string
|
||||
{
|
||||
if ($index_data['Name'] == 'PRIMARY') {
|
||||
$index = 'PRIMARY KEY (%s)';
|
||||
} elseif ($index_data['Unique']) {
|
||||
$index = "UNIQUE `{$index_data['Name']}` (%s)";
|
||||
} else {
|
||||
$index = "INDEX `{$index_data['Name']}` (%s)";
|
||||
}
|
||||
|
||||
$columns = implode(',', array_map(function ($col) {
|
||||
return "`$col`";
|
||||
}, $index_data['Columns']));
|
||||
|
||||
return sprintf($index, $columns);
|
||||
}
|
||||
|
||||
private function addConstraintSql(string $table, array $constraint): string
|
||||
{
|
||||
$sql = "ALTER TABLE `$table` ADD CONSTRAINT `{$constraint['name']}` FOREIGN KEY (`{$constraint['foreign_key']}`) ";
|
||||
$sql .= " REFERENCES `{$constraint['table']}` (`{$constraint['key']}`)";
|
||||
if (! empty($constraint['extra'])) {
|
||||
$sql .= ' ' . $constraint['extra'];
|
||||
}
|
||||
$sql .= ';';
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
private function dropConstraintSql(string $table, string $name): string
|
||||
{
|
||||
return "ALTER TABLE `$table` DROP FOREIGN KEY `$name`;";
|
||||
}
|
||||
}
|
68
LibreNMS/Validations/Database/CheckSqlServerTime.php
Normal file
68
LibreNMS/Validations/Database/CheckSqlServerTime.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/*
|
||||
* CheckSqlServerTime.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 2022 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Validations\Database;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonInterval;
|
||||
use LibreNMS\DB\Eloquent;
|
||||
use LibreNMS\Interfaces\Validation;
|
||||
use LibreNMS\ValidationResult;
|
||||
|
||||
class CheckSqlServerTime implements Validation
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function validate(): ValidationResult
|
||||
{
|
||||
$raw_time = Eloquent::DB()->selectOne('SELECT NOW() as time')->time;
|
||||
$db_time = new Carbon($raw_time);
|
||||
$php_time = Carbon::now();
|
||||
|
||||
$diff = $db_time->diffAsCarbonInterval($php_time);
|
||||
|
||||
if ($diff->compare(CarbonInterval::minute(1)) > 0) {
|
||||
$message = "Time between this server and the mysql database is off\n Mysql time :mysql_time\n PHP time :php_time";
|
||||
$message .= ' Mysql time ' . $db_time->toDateTimeString() . PHP_EOL;
|
||||
$message .= ' PHP time ' . $php_time->toDateTimeString() . PHP_EOL;
|
||||
|
||||
return ValidationResult::fail(trans('validation.validations.database.CheckSqlServerTime.fail', [
|
||||
'mysql_time' => $db_time->toDateTimeString(),
|
||||
'php_time' => $php_time->toDateTimeString(),
|
||||
]));
|
||||
}
|
||||
|
||||
return ValidationResult::ok(trans('validation.validations.database.CheckSqlServerTime.ok'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function enabled(): bool
|
||||
{
|
||||
return Eloquent::isConnected();
|
||||
}
|
||||
}
|
@ -38,7 +38,7 @@ class Dependencies extends BaseValidation
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
public function validate(Validator $validator)
|
||||
public function validate(Validator $validator): void
|
||||
{
|
||||
if (EnvHelper::librenmsDocker()) {
|
||||
$validator->ok('Installed from the official Docker image; no Composer required');
|
||||
|
@ -36,7 +36,7 @@ class Disk extends BaseValidation
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
public function validate(Validator $validator)
|
||||
public function validate(Validator $validator): void
|
||||
{
|
||||
// Disk space and permission checks
|
||||
$temp_dir = Config::get('temp_dir');
|
||||
|
@ -35,11 +35,12 @@ namespace LibreNMS\Validations;
|
||||
use App\Models\PollerCluster;
|
||||
use Carbon\Carbon;
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Validations\Rrd\CheckRrdcachedConnectivity;
|
||||
use LibreNMS\Validator;
|
||||
|
||||
class DistributedPoller extends BaseValidation
|
||||
{
|
||||
public function isDefault()
|
||||
public function isDefault(): bool
|
||||
{
|
||||
// run by default if distributed polling is enabled
|
||||
return Config::get('distributed_poller');
|
||||
@ -51,7 +52,7 @@ class DistributedPoller extends BaseValidation
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
public function validate(Validator $validator)
|
||||
public function validate(Validator $validator): void
|
||||
{
|
||||
if (! Config::get('distributed_poller')) {
|
||||
$validator->fail('You have not enabled distributed_poller', 'lnms config:set distributed_poller true');
|
||||
@ -64,7 +65,7 @@ class DistributedPoller extends BaseValidation
|
||||
} elseif (! is_dir(Config::get('rrd_dir'))) {
|
||||
$validator->fail('You have not configured $config[\'rrd_dir\']');
|
||||
} else {
|
||||
Rrd::checkRrdcached($validator);
|
||||
$validator->result((new CheckRrdcachedConnectivity)->validate(), $this->name);
|
||||
}
|
||||
|
||||
if (PollerCluster::exists()) {
|
||||
@ -82,7 +83,7 @@ class DistributedPoller extends BaseValidation
|
||||
$this->checkPythonWrapper($validator);
|
||||
}
|
||||
|
||||
private function checkDispatcherService(Validator $validator)
|
||||
private function checkDispatcherService(Validator $validator): void
|
||||
{
|
||||
$driver = config('cache.default');
|
||||
if ($driver != 'redis') {
|
||||
@ -109,7 +110,7 @@ class DistributedPoller extends BaseValidation
|
||||
}
|
||||
}
|
||||
|
||||
private function checkPythonWrapper(Validator $validator)
|
||||
private function checkPythonWrapper(Validator $validator): void
|
||||
{
|
||||
if (! Config::get('distributed_poller_memcached_host')) {
|
||||
$validator->fail('You have not configured $config[\'distributed_poller_memcached_host\']');
|
||||
|
@ -38,7 +38,7 @@ class Mail extends BaseValidation
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
public function validate(Validator $validator)
|
||||
public function validate(Validator $validator): void
|
||||
{
|
||||
if (Config::get('alert.transports.mail') === true) {
|
||||
$run_test = 1;
|
||||
|
@ -40,7 +40,7 @@ class Php extends BaseValidation
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
public function validate(Validator $validator)
|
||||
public function validate(Validator $validator): void
|
||||
{
|
||||
$this->checkVersion($validator);
|
||||
$this->checkExtensions($validator);
|
||||
|
@ -37,7 +37,7 @@ class Programs extends BaseValidation
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
public function validate(Validator $validator)
|
||||
public function validate(Validator $validator): void
|
||||
{
|
||||
// Check programs
|
||||
$bins = ['fping', 'rrdtool', 'snmpwalk', 'snmpget', 'snmpgetnext', 'snmpbulkwalk'];
|
||||
|
@ -40,7 +40,7 @@ class Python extends BaseValidation
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
public function validate(Validator $validator)
|
||||
public function validate(Validator $validator): void
|
||||
{
|
||||
$version = Version::get()->python();
|
||||
|
||||
|
@ -25,65 +25,8 @@
|
||||
|
||||
namespace LibreNMS\Validations;
|
||||
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Util\Version;
|
||||
use LibreNMS\Validator;
|
||||
|
||||
class Rrd extends BaseValidation
|
||||
{
|
||||
/**
|
||||
* Validate this module.
|
||||
* To return ValidationResults, call ok, warn, fail, or result methods on the $validator
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
public function validate(Validator $validator)
|
||||
{
|
||||
// Check that rrdtool config version is what we see
|
||||
if (Config::has('rrdtool_version')) {
|
||||
$rrd_version = Version::get()->rrdtool();
|
||||
if (version_compare(Config::get('rrdtool_version'), '1.5.5', '<')
|
||||
&& version_compare(Config::get('rrdtool_version'), $rrd_version, '>')
|
||||
) {
|
||||
$validator->fail(
|
||||
'The rrdtool version you have specified is newer than what is installed.',
|
||||
"Either comment out \$config['rrdtool_version'] = '" .
|
||||
Config::get('rrdtool_version') . "'; or set \$config['rrdtool_version'] = '{$rrd_version}';"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (Config::get('rrdcached')) {
|
||||
self::checkRrdcached($validator);
|
||||
} else {
|
||||
$rrd_dir = Config::get('rrd_dir');
|
||||
|
||||
$dir_stat = stat($rrd_dir);
|
||||
if ($dir_stat[4] == 0 || $dir_stat[5] == 0) {
|
||||
$validator->warn('Your RRD directory is owned by root, please consider changing over to user a non-root user');
|
||||
}
|
||||
|
||||
if (substr(sprintf('%o', fileperms($rrd_dir)), -3) != 775) {
|
||||
$validator->warn('Your RRD directory is not set to 0775', "chmod 775 $rrd_dir");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function checkRrdcached(Validator $validator)
|
||||
{
|
||||
[$host,$port] = explode(':', Config::get('rrdcached'));
|
||||
if ($host == 'unix') {
|
||||
// Using socket, check that file exists
|
||||
if (! file_exists($port)) {
|
||||
$validator->fail("$port doesn't appear to exist, rrdcached test failed");
|
||||
}
|
||||
} else {
|
||||
$connection = @fsockopen($host, (int) $port);
|
||||
if (is_resource($connection)) {
|
||||
fclose($connection);
|
||||
} else {
|
||||
$validator->fail('Cannot connect to rrdcached instance');
|
||||
}
|
||||
}
|
||||
}
|
||||
protected $directory = 'Rrd';
|
||||
protected $name = 'rrd';
|
||||
}
|
||||
|
62
LibreNMS/Validations/Rrd/CheckRrdDirPermissions.php
Normal file
62
LibreNMS/Validations/Rrd/CheckRrdDirPermissions.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/*
|
||||
* CheckRrddirPermissions.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 2022 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Validations\Rrd;
|
||||
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Interfaces\Validation;
|
||||
use LibreNMS\ValidationResult;
|
||||
|
||||
class CheckRrdDirPermissions implements Validation
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function validate(): ValidationResult
|
||||
{
|
||||
$rrd_dir = Config::get('rrd_dir');
|
||||
|
||||
$dir_stat = stat($rrd_dir);
|
||||
if ($dir_stat[4] == 0 || $dir_stat[5] == 0) {
|
||||
return ValidationResult::warn(trans('validation.validations.rrd.CheckRrdDirPermissions.fail_root'),
|
||||
sprintf('chown %s:%s %s', Config::get('user'), Config::get('group'), $rrd_dir)
|
||||
);
|
||||
}
|
||||
|
||||
if (substr(sprintf('%o', fileperms($rrd_dir)), -3) != 775) {
|
||||
return ValidationResult::warn(trans('validation.validations.rrd.CheckRrdDirPermissions.fail_mode'), "chmod 775 $rrd_dir");
|
||||
}
|
||||
|
||||
return ValidationResult::ok(trans('validation.validations.rrd.CheckRrdDirPermissions.ok'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function enabled(): bool
|
||||
{
|
||||
return ! Config::get('rrdcached');
|
||||
}
|
||||
}
|
73
LibreNMS/Validations/Rrd/CheckRrdVersion.php
Normal file
73
LibreNMS/Validations/Rrd/CheckRrdVersion.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/*
|
||||
* CheckRrdVersion.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 2022 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Validations\Rrd;
|
||||
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Illuminate\Support\Str;
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Interfaces\Validation;
|
||||
use LibreNMS\Interfaces\ValidationFixer;
|
||||
use LibreNMS\ValidationResult;
|
||||
use Storage;
|
||||
|
||||
class CheckRrdVersion implements Validation, ValidationFixer
|
||||
{
|
||||
public function validate(): ValidationResult
|
||||
{
|
||||
// Check that rrdtool config version is what we see
|
||||
$rrd_version = '0.1'; //Version::get()->rrdtool();
|
||||
if (version_compare(Config::get('rrdtool_version'), '1.5.5', '<')
|
||||
&& version_compare(Config::get('rrdtool_version'), $rrd_version, '>')
|
||||
) {
|
||||
return ValidationResult::fail(
|
||||
trans('validation.validations.rrd.CheckRrdVersion.fail'),
|
||||
trans('validation.validations.rrd.CheckRrdVersion.fix', ['version' => Config::get('rrdtool_version')])
|
||||
)->setFixer(__CLASS__, is_writable(base_path('config.php')));
|
||||
}
|
||||
|
||||
return ValidationResult::ok(trans('validation.validations.rrd.CheckRrdVersion.ok'));
|
||||
}
|
||||
|
||||
public function enabled(): bool
|
||||
{
|
||||
return Config::has('rrdtool_version');
|
||||
}
|
||||
|
||||
public function fix(): bool
|
||||
{
|
||||
try {
|
||||
$contents = Storage::disk('base')->get('config.php');
|
||||
|
||||
$lines = array_filter(explode("\n", $contents), function ($line) {
|
||||
return ! Str::contains($line, ['$config[\'rrdtool_version\']', '$config["rrdtool_version"]']);
|
||||
});
|
||||
|
||||
return Storage::disk('base')->put('config.php', implode("\n", $lines));
|
||||
} catch (FileNotFoundException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
64
LibreNMS/Validations/Rrd/CheckRrdcachedConnectivity.php
Normal file
64
LibreNMS/Validations/Rrd/CheckRrdcachedConnectivity.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/*
|
||||
* CheckRrdcachedConnectivity.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 2022 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Validations\Rrd;
|
||||
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Interfaces\Validation;
|
||||
use LibreNMS\ValidationResult;
|
||||
|
||||
class CheckRrdcachedConnectivity implements Validation
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function validate(): ValidationResult
|
||||
{
|
||||
[$host,$port] = explode(':', Config::get('rrdcached'));
|
||||
if ($host == 'unix') {
|
||||
// Using socket, check that file exists
|
||||
if (! file_exists($port)) {
|
||||
return ValidationResult::fail(trans('validation.validations.rrd.CheckRrdcachedConnectivity.fail_socket', ['socket' => $port]));
|
||||
}
|
||||
} else {
|
||||
$connection = @fsockopen($host, (int) $port);
|
||||
if (is_resource($connection)) {
|
||||
fclose($connection);
|
||||
} else {
|
||||
return ValidationResult::fail(trans('validation.validations.rrd.CheckRrdcachedConnectivity.fail_port', ['port' => $port]));
|
||||
}
|
||||
}
|
||||
|
||||
return ValidationResult::ok(trans('validation.validations.rrd.CheckRrdcachedConnectivity.ok'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function enabled(): bool
|
||||
{
|
||||
return (bool) Config::get('rrdcached');
|
||||
}
|
||||
}
|
@ -41,7 +41,7 @@ class RrdCheck extends BaseValidation
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
public function validate(Validator $validator)
|
||||
public function validate(Validator $validator): void
|
||||
{
|
||||
// Loop through the rrd_dir
|
||||
$rrd_directory = new RecursiveDirectoryIterator(Config::get('rrd_dir'));
|
||||
|
@ -36,7 +36,7 @@ class System extends BaseValidation
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate(Validator $validator)
|
||||
public function validate(Validator $validator): void
|
||||
{
|
||||
$install_dir = $validator->getBaseDir();
|
||||
|
||||
|
@ -38,7 +38,7 @@ use LibreNMS\Validator;
|
||||
|
||||
class Updates extends BaseValidation
|
||||
{
|
||||
public function validate(Validator $validator)
|
||||
public function validate(Validator $validator): void
|
||||
{
|
||||
if (EnvHelper::librenmsDocker()) {
|
||||
$validator->warn('Updates are managed through the official Docker image');
|
||||
|
@ -41,7 +41,7 @@ class User extends BaseValidation
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
public function validate(Validator $validator)
|
||||
public function validate(Validator $validator): void
|
||||
{
|
||||
// Check we are running this as the root user
|
||||
$username = $validator->getUsername();
|
||||
|
@ -154,6 +154,17 @@ class Validator
|
||||
|
||||
foreach ($results as $result) {
|
||||
$result->consolePrint();
|
||||
if ($result->hasFixer()) {
|
||||
$input = readline('Attempt to fix this issue (y or n)?:');
|
||||
if ($input === 'y') {
|
||||
$result = app()->make($result->getFixer())->fix();
|
||||
if ($result) {
|
||||
echo "Attempted to apply fix.\n";
|
||||
} else {
|
||||
echo "Failed to apply fix.\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,8 @@ namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use LibreNMS\Interfaces\ValidationFixer;
|
||||
use LibreNMS\Validator;
|
||||
|
||||
class ValidateController extends Controller
|
||||
@ -20,4 +22,23 @@ class ValidateController extends Controller
|
||||
|
||||
return response()->json($validator->toArray());
|
||||
}
|
||||
|
||||
public function runFixer(Request $request): JsonResponse
|
||||
{
|
||||
$this->validate($request, [
|
||||
'fixer' => [
|
||||
'starts_with:LibreNMS\Validations',
|
||||
function ($attribute, $value, $fail) {
|
||||
if (! class_exists($value) || ! in_array(ValidationFixer::class, class_implements($value))) {
|
||||
$fail(trans('validation.results.invalid_fixer'));
|
||||
}
|
||||
},
|
||||
],
|
||||
]);
|
||||
$fixer = $request->get('fixer');
|
||||
|
||||
return response()->json([
|
||||
'result' => (new $fixer)->fix(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,11 @@ return [
|
||||
'root' => storage_path('app'),
|
||||
],
|
||||
|
||||
'base' => [
|
||||
'driver' => 'local',
|
||||
'root' => base_path(),
|
||||
],
|
||||
|
||||
'public' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/public'),
|
||||
|
@ -58,7 +58,7 @@ class ConfigSeeder extends Seeder
|
||||
}
|
||||
|
||||
if (\App\Models\Config::exists()) {
|
||||
if (! $this->command->confirm(trans('commands.db:seed.existing_config'), false)) {
|
||||
if (! app()->runningInConsole() || ! $this->command->confirm(trans('commands.db:seed.existing_config'), false)) {
|
||||
return; // don't overwrite existing settings.
|
||||
}
|
||||
}
|
||||
|
@ -3340,11 +3340,6 @@ parameters:
|
||||
count: 1
|
||||
path: LibreNMS/Interfaces/Polling/SlaPolling.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Interfaces\\\\ValidationGroup\\:\\:validate\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Interfaces/ValidationGroup.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Model\\:\\:clean\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
@ -6540,266 +6535,6 @@ parameters:
|
||||
count: 1
|
||||
path: LibreNMS/Util/Validate.php
|
||||
|
||||
-
|
||||
message: "#^Property LibreNMS\\\\Validations\\\\BaseValidation\\:\\:\\$RUN_BY_DEFAULT has no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/BaseValidation.php
|
||||
|
||||
-
|
||||
message: "#^Property LibreNMS\\\\Validations\\\\BaseValidation\\:\\:\\$completed has no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/BaseValidation.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Configuration\\:\\:validate\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Configuration.php
|
||||
|
||||
-
|
||||
message: "#^Comparison operation \"\\>\" between int\\<1001, max\\> and 1000 is always true\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addColumnSql\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addColumnSql\\(\\) has parameter \\$previous_column with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addColumnSql\\(\\) has parameter \\$primary with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addColumnSql\\(\\) has parameter \\$schema with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addColumnSql\\(\\) has parameter \\$table with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addConstraintSql\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addConstraintSql\\(\\) has parameter \\$constraint with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addConstraintSql\\(\\) has parameter \\$table with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addIndexSql\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addIndexSql\\(\\) has parameter \\$index_schema with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addIndexSql\\(\\) has parameter \\$table with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addTableSql\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addTableSql\\(\\) has parameter \\$table with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addTableSql\\(\\) has parameter \\$table_schema with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:checkCollation\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:checkMode\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:checkMysqlEngine\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:checkSchema\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:checkTime\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:checkVersion\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropColumnSql\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropColumnSql\\(\\) has parameter \\$column with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropColumnSql\\(\\) has parameter \\$table with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropConstraintSql\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropConstraintSql\\(\\) has parameter \\$name with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropConstraintSql\\(\\) has parameter \\$table with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropIndexSql\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropIndexSql\\(\\) has parameter \\$name with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropIndexSql\\(\\) has parameter \\$table with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropTableSql\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropTableSql\\(\\) has parameter \\$table with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:updateIndexSql\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:updateIndexSql\\(\\) has parameter \\$index_schema with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:updateIndexSql\\(\\) has parameter \\$name with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:updateIndexSql\\(\\) has parameter \\$table with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:updateTableSql\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:updateTableSql\\(\\) has parameter \\$column with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:updateTableSql\\(\\) has parameter \\$column_schema with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:updateTableSql\\(\\) has parameter \\$table with no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:validate\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:validateSystem\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Database.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Dependencies\\:\\:validate\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Dependencies.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Disk\\:\\:validate\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Disk.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\DistributedPoller\\:\\:checkDispatcherService\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/DistributedPoller.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\DistributedPoller\\:\\:checkPythonWrapper\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/DistributedPoller.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\DistributedPoller\\:\\:validate\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/DistributedPoller.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Mail\\:\\:validate\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Mail.php
|
||||
|
||||
-
|
||||
message: "#^Property LibreNMS\\\\Validations\\\\Mail\\:\\:\\$RUN_BY_DEFAULT has no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Mail.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Php\\:\\:checkExtensions\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
@ -6820,11 +6555,6 @@ parameters:
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Php.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Php\\:\\:validate\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Php.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Programs\\:\\:checkFping6\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
@ -6875,11 +6605,6 @@ parameters:
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Programs.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Programs\\:\\:validate\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Programs.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Python\\:\\:checkExtensions\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
@ -6905,51 +6630,6 @@ parameters:
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Python.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Python\\:\\:validate\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Python.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Rrd\\:\\:checkRrdcached\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Rrd.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Rrd\\:\\:validate\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Rrd.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\RrdCheck\\:\\:validate\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/RrdCheck.php
|
||||
|
||||
-
|
||||
message: "#^Property LibreNMS\\\\Validations\\\\RrdCheck\\:\\:\\$RUN_BY_DEFAULT has no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/RrdCheck.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\System\\:\\:validate\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/System.php
|
||||
|
||||
-
|
||||
message: "#^Property LibreNMS\\\\Validations\\\\System\\:\\:\\$RUN_BY_DEFAULT has no type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/System.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\Updates\\:\\:validate\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/Updates.php
|
||||
|
||||
-
|
||||
message: "#^Method LibreNMS\\\\Validations\\\\User\\:\\:validate\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Validations/User.php
|
||||
|
||||
-
|
||||
message: "#^Property App\\\\Models\\\\Device\\:\\:\\$authlevel \\('authNoPriv'\\|'authPriv'\\|'noAuthNoPriv'\\) does not accept null\\.$#"
|
||||
count: 1
|
||||
|
@ -160,12 +160,66 @@ return [
|
||||
'attributes' => [],
|
||||
|
||||
'results' => [
|
||||
'autofix' => 'Attempt to automatically fix',
|
||||
'fix' => 'Fix',
|
||||
'fixed' => 'Fix has completed, refresh to re-run validations.',
|
||||
'fetch_failed' => 'Failed to fetch validation results',
|
||||
'backend_failed' => 'Failed to load data from backend, check webserver.',
|
||||
'invalid_fixer' => 'Invalid Fixer',
|
||||
'show_all' => 'Show all',
|
||||
'show_less' => 'Show less',
|
||||
'validate' => 'Validate',
|
||||
'validating' => 'Validating',
|
||||
],
|
||||
'validations' => [
|
||||
'rrd' => [
|
||||
'CheckRrdVersion' => [
|
||||
'fail' => 'The rrdtool version you have specified is newer than what is installed.',
|
||||
'fix' => 'Either comment out or delete $config[\'rrdtool_version\'] = \':version\'; from your config.php file',
|
||||
'ok' => 'rrdtool version ok',
|
||||
],
|
||||
'CheckRrdcachedConnectivity' => [
|
||||
'fail_socket' => ':socket does not appear to exist, rrdcached connectivity test failed',
|
||||
'fail_port' => 'Cannot connect to rrdcached server on port :port',
|
||||
'ok' => 'Connected to rrdcached',
|
||||
],
|
||||
'CheckRrdDirPermissions' => [
|
||||
'fail_root' => 'Your RRD directory is owned by root, please consider changing over to user a non-root user',
|
||||
'fail_mode' => 'Your RRD directory is not set to 0775',
|
||||
'ok' => 'rrd_dir is writable',
|
||||
],
|
||||
],
|
||||
'database' => [
|
||||
'CheckDatabaseTableNamesCase' => [
|
||||
'fail' => 'You have lower_case_table_names set to 1 or true in mysql config.',
|
||||
'fix' => 'Set lower_case_table_names=0 in your mysql config file in the [mysqld] section.',
|
||||
'ok' => 'lower_case_table_names is enabled',
|
||||
],
|
||||
'CheckDatabaseServerVersion' => [
|
||||
'fail' => ':server version :min is the minimum supported version as of :date.',
|
||||
'fix' => 'Update :server to a supported version, :suggested suggested.',
|
||||
'ok' => 'SQL Server meets minimum requirements',
|
||||
],
|
||||
'CheckMysqlEngine' => [
|
||||
'fail' => 'Some tables are not using the recommended InnoDB engine, this may cause you issues.',
|
||||
'tables' => 'Tables',
|
||||
'ok' => 'MySQL engine is optimal',
|
||||
],
|
||||
'CheckSqlServerTime' => [
|
||||
'fail' => "Time between this server and the mysql database is off\n Mysql time :mysql_time\n PHP time :php_time",
|
||||
'ok' => 'MySQl and PHP time match',
|
||||
],
|
||||
'CheckSchemaVersion' => [
|
||||
'fail_outdated' => 'Your database is out of date!',
|
||||
'fail_legacy_outdated' => 'Your database schema (:current) is older than the latest (:latest).',
|
||||
'fix_legacy_outdated' => 'Manually run ./daily.sh, and check for any errors.',
|
||||
'warn_extra_migrations' => 'Your database schema has extra migrations (:migrations). If you just switched to the stable release from the daily release, your database is in between releases and this will be resolved with the next release.',
|
||||
'warn_legacy_newer' => 'Your database schema (:current) is newer than expected (:latest). If you just switched to the stable release from the daily release, your database is in between releases and this will be resolved with the next release.',
|
||||
'ok' => 'Database Schema is current',
|
||||
],
|
||||
'CheckSchemaCollation' => [
|
||||
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
@ -36,7 +36,13 @@
|
||||
<div class="panel-heading"
|
||||
x-text="result.statusText + ': ' + result.message"
|
||||
></div>
|
||||
<div class="panel-body" x-show="result.fix.length || result.list.length">
|
||||
<div class="panel-body" x-show="result.fix.length || result.list.length || result.fixer">
|
||||
<div x-show="result.fixer" class="tw-mb-2" x-data="fixerData(result.fixer)">
|
||||
<button class="btn btn-success" x-on:click="runFixer" x-bind:disabled="running" x-show="! fixed">
|
||||
<i class="fa-solid" x-bind:class="running ? 'fa-spinner fa-spin' : 'fa-wrench'"></i> {{ __('validation.results.autofix') }}
|
||||
</button>
|
||||
<div x-show="fixed">{{ __('validation.results.fixed') }}</div>
|
||||
</div>
|
||||
<div x-show="result.fix.length">
|
||||
{{ __('validation.results.fix') }}: <pre x-text='result.fix.join("\r\n")'>
|
||||
</pre>
|
||||
@ -71,6 +77,30 @@
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
|
||||
function fixerData(name) {
|
||||
return {
|
||||
running: false,
|
||||
fixed: false,
|
||||
fixer: name,
|
||||
runFixer() {
|
||||
event.target.disabled = true;
|
||||
fetch('{{ route('validate.fix') }}', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({fixer: this.fixer}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
"X-CSRF-Token": document.querySelector('input[name=_token]').value
|
||||
},
|
||||
}).then(response => {
|
||||
if (response.ok) {
|
||||
this.fixed = true;
|
||||
} else {
|
||||
this.running = false;
|
||||
}
|
||||
}).catch(response => this.running = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
|
@ -98,6 +98,7 @@ Route::group(['middleware' => ['auth'], 'guard' => 'auth'], function () {
|
||||
Route::resource('port-groups', 'PortGroupController');
|
||||
Route::get('validate', [\App\Http\Controllers\ValidateController::class, 'index'])->name('validate');
|
||||
Route::get('validate/results', [\App\Http\Controllers\ValidateController::class, 'runValidation'])->name('validate.results');
|
||||
Route::post('validate/fix', [\App\Http\Controllers\ValidateController::class, 'runFixer'])->name('validate.fix');
|
||||
});
|
||||
|
||||
Route::get('plugin', 'PluginLegacyController@redirect');
|
||||
|
58
tests/Unit/ValidationFixTest.php
Normal file
58
tests/Unit/ValidationFixTest.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/*
|
||||
* ValidationFixTest.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 2022 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Tests\Unit;
|
||||
|
||||
use LibreNMS\Tests\TestCase;
|
||||
use LibreNMS\Validations\Rrd\CheckRrdVersion;
|
||||
use Storage;
|
||||
|
||||
class ValidationFixTest extends TestCase
|
||||
{
|
||||
public function testRrdVersionFix(): void
|
||||
{
|
||||
Storage::fake('base');
|
||||
Storage::disk('base')->put('config.php', <<<'EOF'
|
||||
<?php
|
||||
$config['test'] = 'rrdtool_version';
|
||||
$config['rrdtool_version'] = '1.0';
|
||||
$config["rrdtool_version"] = '1.1';
|
||||
# comment
|
||||
|
||||
EOF
|
||||
);
|
||||
|
||||
(new CheckRrdVersion())->fix();
|
||||
|
||||
$actual = Storage::disk('base')->get('config.php');
|
||||
$this->assertSame(<<<'EOF'
|
||||
<?php
|
||||
$config['test'] = 'rrdtool_version';
|
||||
# comment
|
||||
|
||||
EOF, $actual);
|
||||
Storage::disk('base')->delete('config.php');
|
||||
}
|
||||
}
|
@ -172,13 +172,13 @@ EOF;
|
||||
// output matches that of ValidationResult
|
||||
function print_fail($msg, $fix = null)
|
||||
{
|
||||
c_echo("[%RFAIL%n] $msg");
|
||||
echo "[\033[31;1mFAIL\033[0m] $msg";
|
||||
if ($fix && strlen($msg) > 72) {
|
||||
echo PHP_EOL . ' ';
|
||||
}
|
||||
|
||||
if (! empty($fix)) {
|
||||
c_echo(" [%BFIX%n] %B$fix%n");
|
||||
echo " [\033[34;1mFIX\033[0m] \033[34;1m$fix\033[0m";
|
||||
}
|
||||
echo PHP_EOL;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user