mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
Rewrite development helper to lnms dev:check (#11650)
* Refactor pre-commit to class * docs build * dusk check * ci mode for checks * full checks * other mysql * make other lint checks actually work fix pylint finding * ci is a long opt * fix undefined index * dusk fully working * ask for forgiveness, not permission * fix whitespace * skip dusk sometimes * Handle 3com and other os with digits * flags instead of if else spaghetti * convert to command * cleanup * missed check * fixes * case * self-check :D * argument now * fix bugs from refactors * another fix * adjust file change parsing * refactor execut a bit * fallback to global quiet when unknown type. * allow quiet override for specific commands * output cleanup * check flow * start of tests * file categorizer tests and fixes * fixes and cleanup * skipable not implemented... * more tests, fix bugs * more tests and cleanup * wrong command * fix canCheck and set env properly * full env fix * don't allow dusk on user's run as it will erase their db. * fix os option * fix whitespace * don't need to start server * ci doesn't like that
This commit is contained in:
71
LibreNMS/Util/Categorizer.php
Normal file
71
LibreNMS/Util/Categorizer.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/**
|
||||
* Categorizer.php
|
||||
*
|
||||
* Categorize a list of items according to a dynamic list
|
||||
*
|
||||
* 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 2020 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Util;
|
||||
|
||||
class Categorizer
|
||||
{
|
||||
protected $items;
|
||||
protected $categorized = [];
|
||||
protected $categories = [];
|
||||
protected $skippable;
|
||||
|
||||
public function __construct($items = [])
|
||||
{
|
||||
$this->skippable = function ($item) {
|
||||
return false;
|
||||
};
|
||||
$this->items = $items;
|
||||
}
|
||||
|
||||
public function addCategory(string $category, callable $function)
|
||||
{
|
||||
$this->categories[$category] = $function;
|
||||
$this->categorized[$category] = [];
|
||||
}
|
||||
|
||||
public function setSkippable(callable $function)
|
||||
{
|
||||
$this->skippable = $function;
|
||||
}
|
||||
|
||||
public function categorize()
|
||||
{
|
||||
foreach ($this->items as $item) {
|
||||
foreach ($this->categories as $category => $test) {
|
||||
if (call_user_func($this->skippable, $item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result = call_user_func($test, $item);
|
||||
if ($result !== false) {
|
||||
$this->categorized[$category][] = $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->categorized;
|
||||
}
|
||||
}
|
521
LibreNMS/Util/CiHelper.php
Normal file
521
LibreNMS/Util/CiHelper.php
Normal file
@@ -0,0 +1,521 @@
|
||||
<?php
|
||||
/**
|
||||
* CiHelper.php
|
||||
*
|
||||
* Code for CI operation
|
||||
*
|
||||
* 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 2020 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Util;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class CiHelper
|
||||
{
|
||||
private $changedFiles;
|
||||
private $changed;
|
||||
private $os;
|
||||
private $unitEnv = [];
|
||||
private $duskEnv = ['APP_ENV' => 'testing'];
|
||||
|
||||
private $completedChecks = [
|
||||
'lint' => false,
|
||||
'style' => false,
|
||||
'unit' => false,
|
||||
'web' => false,
|
||||
];
|
||||
private $ciDefaults = [
|
||||
'quiet' => [
|
||||
'lint' => true,
|
||||
'style' => true,
|
||||
'unit' => false,
|
||||
'web' => false,
|
||||
],
|
||||
];
|
||||
private $flags = [
|
||||
'lint_enable' => true,
|
||||
'style_enable' => true,
|
||||
'unit_enable' => true,
|
||||
'web_enable' => false,
|
||||
'lint_skip' => false,
|
||||
'style_skip' => false,
|
||||
'unit_skip' => false,
|
||||
'web_skip' => false,
|
||||
'lint_skip_php' => false,
|
||||
'lint_skip_python' => false,
|
||||
'lint_skip_bash' => false,
|
||||
'unit_os' => false,
|
||||
'unit_docs' => false,
|
||||
'unit_svg' => false,
|
||||
'unit_modules' => false,
|
||||
'docs_changed' => false,
|
||||
'ci' => false,
|
||||
'commands' => false,
|
||||
'fail-fast' => false,
|
||||
'full' => false,
|
||||
'quiet' => false,
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function enable($check, $enabled = true)
|
||||
{
|
||||
$this->flags["{$check}_enable"] = $enabled;
|
||||
}
|
||||
|
||||
public function duskHeadless()
|
||||
{
|
||||
$this->duskEnv['CHROME_HEADLESS'] = 1;
|
||||
}
|
||||
|
||||
public function enableDb()
|
||||
{
|
||||
$this->unitEnv['DBTEST'] = 1;
|
||||
}
|
||||
|
||||
public function enableSnmpsim()
|
||||
{
|
||||
$this->unitEnv['SNMPSIM'] = 1;
|
||||
}
|
||||
|
||||
public function setModules(array $modules)
|
||||
{
|
||||
$this->unitEnv['TEST_MODULES'] = implode(',', $modules);
|
||||
$this->flags['unit_modules'] = true;
|
||||
$this->enableDb();
|
||||
$this->enableSnmpsim();
|
||||
}
|
||||
|
||||
public function setOS(array $os)
|
||||
{
|
||||
$this->os = $os;
|
||||
$this->flags['unit_os'] = true;
|
||||
$this->enableDb();
|
||||
$this->enableSnmpsim();
|
||||
}
|
||||
|
||||
public function setFlags(array $flags)
|
||||
{
|
||||
foreach (array_intersect_key($flags, $this->flags) as $key => $value) {
|
||||
$this->flags[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
$return = 0;
|
||||
foreach (array_keys($this->completedChecks) as $check) {
|
||||
$ret = $this->runCheck($check);
|
||||
|
||||
if ($this->flags['fail-fast'] && $ret !== 0 && $ret !== 250) {
|
||||
return $return;
|
||||
} else {
|
||||
$return += $ret;
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that all possible checks have been completed
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function allChecksComplete()
|
||||
{
|
||||
return array_reduce($this->completedChecks, function ($result, $check) {
|
||||
return $result && $check;
|
||||
}, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a flag value
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public function getFlag($name)
|
||||
{
|
||||
return $this->flags[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all flags
|
||||
* @return bool[]
|
||||
*/
|
||||
public function getFlags()
|
||||
{
|
||||
return $this->flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs phpunit
|
||||
*
|
||||
* @return int the return value from phpunit (0 = success)
|
||||
*/
|
||||
public function checkUnit()
|
||||
{
|
||||
$phpunit_bin = $this->checkPhpExec('phpunit');
|
||||
|
||||
$phpunit_cmd = "$phpunit_bin --colors=always";
|
||||
|
||||
if ($this->flags['fail-fast']) {
|
||||
$phpunit_cmd .= ' --stop-on-error --stop-on-failure';
|
||||
}
|
||||
|
||||
// exclusive tests
|
||||
if ($this->flags['unit_os']) {
|
||||
$selected_os = $this->os ?: $this->changed['os'];
|
||||
echo 'Only checking os: ' . implode(', ', $selected_os) . PHP_EOL;
|
||||
$filter = implode('.*|', $selected_os);
|
||||
// include tests that don't have data providers and only data sets that match
|
||||
$phpunit_cmd .= " --group os --filter '/::test[A-Za-z]+$|::test[A-Za-z]+ with data set \"$filter.*\"$/'";
|
||||
} elseif ($this->flags['unit_docs']) {
|
||||
$phpunit_cmd .= " --group docs";
|
||||
} elseif ($this->flags['unit_svg']) {
|
||||
$phpunit_cmd .= ' tests/SVGTest.php';
|
||||
} elseif ($this->flags['unit_modules']) {
|
||||
$phpunit_cmd .= ' tests/OSModulesTest.php';
|
||||
}
|
||||
|
||||
return $this->execute('unit', $phpunit_cmd, false, $this->unitEnv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs phpcs --standard=PSR2 against the code base
|
||||
*
|
||||
* @return int the return value from phpcs (0 = success)
|
||||
*/
|
||||
public function checkStyle()
|
||||
{
|
||||
$phpcs_bin = $this->checkPhpExec('phpcs');
|
||||
|
||||
$files = ($this->flags['full']) ? './' : implode(' ', $this->changed['php']);
|
||||
|
||||
$cs_cmd = "$phpcs_bin -n -p --colors --extensions=php --standard=misc/phpcs_librenms.xml $files";
|
||||
|
||||
return $this->execute('style', $cs_cmd);
|
||||
}
|
||||
|
||||
public function checkWeb()
|
||||
{
|
||||
if (!$this->getFlag('ci')) {
|
||||
echo "Warning: dusk may erase your primary database, do not use yet\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($this->canCheck('web')) {
|
||||
echo "Preparing for web checks\n";
|
||||
$this->execute('config:clear', ['php', 'artisan', 'config:clear'], true);
|
||||
$this->execute('dusk:update', ['php', 'artisan', 'dusk:update', '--detect'], true);
|
||||
|
||||
// check if web server is running
|
||||
$server = new Process(['php', '-S', '127.0.0.1:8000', base_path('server.php')], public_path(), ['APP_ENV' => 'dusk.testing']);
|
||||
$server->setTimeout(3600)
|
||||
->setIdleTimeout(3600)
|
||||
->start();
|
||||
$server->waitUntil(function ($type, $output) {
|
||||
return strpos($output, 'Development Server (http://127.0.0.1:8000) started') !== false;
|
||||
});
|
||||
if ($server->isRunning()) {
|
||||
echo "Started server http://127.0.0.1:8000\n";
|
||||
}
|
||||
}
|
||||
|
||||
$dusk_cmd = "php artisan dusk";
|
||||
|
||||
if ($this->flags['fail-fast']) {
|
||||
$dusk_cmd .= ' --stop-on-error --stop-on-failure';
|
||||
}
|
||||
|
||||
return $this->execute('web', $dusk_cmd, false, $this->duskEnv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs php -l and tests for any syntax errors
|
||||
*
|
||||
* @return int the return value from running php -l (0 = success)
|
||||
*/
|
||||
public function checkLint()
|
||||
{
|
||||
$return = 0;
|
||||
if (!$this->flags['lint_skip_php']) {
|
||||
$parallel_lint_bin = $this->checkPhpExec('parallel-lint');
|
||||
|
||||
// matches a substring of the relative path, leading / is treated as absolute path
|
||||
$lint_excludes = ['vendor/'];
|
||||
$lint_exclude = $this->buildPhpLintExcludes('--exclude ', $lint_excludes);
|
||||
|
||||
$files = $this->flags['full'] ? './' : implode(' ', $this->changed['php']);
|
||||
|
||||
$php_lint_cmd = "$parallel_lint_bin $lint_exclude $files";
|
||||
|
||||
$return += $this->execute('PHP lint', $php_lint_cmd);
|
||||
}
|
||||
|
||||
if (!$this->flags['lint_skip_python']) {
|
||||
$pylint_bin = $this->checkPythonExec('pylint');
|
||||
|
||||
$files = $this->flags['full']
|
||||
? str_replace(PHP_EOL, ' ', rtrim(shell_exec("find . -name '*.py' -not -path './vendor/*' -not -path './tests/*'")))
|
||||
: implode(' ', $this->changed['python']);
|
||||
|
||||
$py_lint_cmd = "$pylint_bin -E -j 0 $files";
|
||||
$return += $this->execute('Python lint', $py_lint_cmd);
|
||||
}
|
||||
|
||||
if (!$this->flags['lint_skip_bash']) {
|
||||
$files = $this->flags['full']
|
||||
? explode(PHP_EOL, rtrim(shell_exec("find . -name '*.sh' -not -path './node_modules/*' -not -path './vendor/*'")))
|
||||
: $this->changed['bash'];
|
||||
|
||||
$bash_cmd = implode(' && ', array_map(function ($file) {
|
||||
return "bash -n $file";
|
||||
}, $files));
|
||||
$return += $this->execute('Bash lint', $bash_cmd);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the specified check and return the return value.
|
||||
* Make sure it isn't skipped by SKIP_TYPE_CHECK env variable and hasn't been run already
|
||||
*
|
||||
* @param string $type type of check lint, style, or unit
|
||||
* @return int the return value from the check (0 = success)
|
||||
*/
|
||||
private function runCheck($type)
|
||||
{
|
||||
if ($method = $this->canCheck($type)) {
|
||||
$ret = $this->$method();
|
||||
$this->completedChecks[$type] = true;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
if ($this->flags["{$type}_skip"]) {
|
||||
echo ucfirst($type) . " check skipped.\n";
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @return false|string the method name to run
|
||||
*/
|
||||
private function canCheck($type)
|
||||
{
|
||||
if ($this->flags["{$type}_skip"] || $this->completedChecks[$type]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$method = 'check' . ucfirst($type);
|
||||
if (method_exists($this, $method) && $this->flags["{$type}_enable"]) {
|
||||
return $method;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a check command
|
||||
*
|
||||
* @param string $name name for status output
|
||||
* @param string|array $command
|
||||
* @param bool $silence silence the status ouput (still shows error output)
|
||||
* @param array $env environment to set
|
||||
* @return int
|
||||
*/
|
||||
private function execute(string $name, $command, $silence = false, $env = null): int
|
||||
{
|
||||
$start = microtime(true);
|
||||
$proc = new Process($command, null, $env);
|
||||
|
||||
if ($this->flags['commands']) {
|
||||
$prefix = '';
|
||||
if ($env) {
|
||||
$prefix .= http_build_query($env, '', ' ') . ' ';
|
||||
}
|
||||
|
||||
echo $prefix . $proc->getCommandLine() . PHP_EOL;
|
||||
return 250;
|
||||
}
|
||||
|
||||
if (!$silence) {
|
||||
echo "Running $name check... ";
|
||||
}
|
||||
|
||||
$space = strrpos($name, ' ');
|
||||
$type = substr($name, $space ? $space + 1 : 0);
|
||||
$quiet = ($this->flags['ci'] && isset($this->ciDefaults['quiet'][$type])) ? $this->ciDefaults['quiet'][$type] : $this->flags['quiet'];
|
||||
|
||||
$proc->setTimeout(3600)->setIdleTimeout(3600);
|
||||
if (!($silence || $quiet)) {
|
||||
echo PHP_EOL;
|
||||
$proc->setTty(Process::isTtySupported());
|
||||
}
|
||||
|
||||
$proc->run();
|
||||
|
||||
$duration = sprintf('%.2fs', microtime(true) - $start);
|
||||
if ($proc->getExitCode() > 0) {
|
||||
if (!$silence) {
|
||||
echo "failed ($duration)\n";
|
||||
}
|
||||
if ($quiet || $silence) {
|
||||
echo $proc->getOutput() . PHP_EOL;
|
||||
echo $proc->getErrorOutput() . PHP_EOL;
|
||||
}
|
||||
} elseif (!$silence) {
|
||||
echo "success ($duration)\n";
|
||||
}
|
||||
|
||||
return $proc->getExitCode();
|
||||
}
|
||||
|
||||
|
||||
public function checkEnvSkips()
|
||||
{
|
||||
$this->flags['unit_skip'] = (bool)getenv('SKIP_UNIT_CHECK');
|
||||
$this->flags['lint_skip'] = (bool)getenv('SKIP_LINT_CHECK');
|
||||
$this->flags['web_skip'] = (bool)getenv('SKIP_WEB_CHECK');
|
||||
$this->flags['style_skip'] = (bool)getenv('SKIP_STYLE_CHECK');
|
||||
}
|
||||
|
||||
public function detectChangedFiles()
|
||||
{
|
||||
$files = trim(getenv('FILES'));
|
||||
$changed_files = $files ?: shell_exec("git diff --diff-filter=d --name-only master | tr '\n' ' '|sed 's/,*$//g'");
|
||||
$this->changedFiles = $changed_files ? explode(' ', $changed_files) : [];
|
||||
|
||||
$this->changed = (new FileCategorizer($this->changedFiles))->categorize();
|
||||
|
||||
$this->parseChangedFiles();
|
||||
}
|
||||
|
||||
private function parseChangedFiles()
|
||||
{
|
||||
if (empty($this->changedFiles) || $this->flags['full']) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
$this->setFlags([
|
||||
'lint_skip_php' => empty($this->changed['php']),
|
||||
'lint_skip_python' => empty($this->changed['python']),
|
||||
'lint_skip_bash' => empty($this->changed['bash']),
|
||||
'unit_os' => $this->getFlag('unit_os') || (!empty($this->changed['os']) && empty(array_diff($this->changed['php'], $this->changed['os-files']))),
|
||||
'unit_docs' => !empty($this->changed['docs']) && empty($this->changed['php']),
|
||||
'unit_svg' => !empty($this->changed['svg']) && empty($this->changed['php']),
|
||||
'docs_changed' => !empty($this->changed['docs']),
|
||||
'full' => !empty($this->changed['full-checks']),
|
||||
]);
|
||||
|
||||
$this->setFlags([
|
||||
'unit_skip' => empty($this->changed['php']) && !array_sum(Arr::only($this->getFlags(), ['unit_os', 'unit_docs', 'unit_svg', 'unit_modules', 'docs_changed'])),
|
||||
'lint_skip' => array_sum(Arr::only($this->getFlags(), ['lint_skip_php', 'lint_skip_python', 'lint_skip_bash'])) === 3,
|
||||
'style_skip' => empty($this->changed['php']),
|
||||
'web_skip' => empty($this->changed['php']) && empty($this->changed['resources']),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for a PHP executable and return the path to it
|
||||
* If it does not exist, run composer.
|
||||
* If composer isn't installed, print error and exit.
|
||||
*
|
||||
* @param string $exec the name of the executable to check
|
||||
* @return string path to the executable
|
||||
*/
|
||||
private function checkPhpExec($exec)
|
||||
{
|
||||
$path = "vendor/bin/$exec";
|
||||
|
||||
if (is_executable($path)) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
echo "Running composer install to install developer dependencies.\n";
|
||||
passthru("scripts/composer_wrapper.php install");
|
||||
|
||||
if (is_executable($path)) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
echo "\nRunning installing deps with composer failed.\n You should try running './scripts/composer_wrapper.php install' by hand\n";
|
||||
echo "You can find more info at http://docs.librenms.org/Developing/Validating-Code/\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for a Python executable and return the path to it
|
||||
* If it does not exist, run pip3.
|
||||
* If pip3 isn't installed, print error and exit.
|
||||
*
|
||||
* @param string $exec the name of the executable to check
|
||||
* @return string path to the executable
|
||||
*/
|
||||
private function checkPythonExec($exec)
|
||||
{
|
||||
$home = getenv('HOME');
|
||||
$path = "$home/.local/bin/$exec";
|
||||
|
||||
if (is_executable($path)) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
// check system
|
||||
$system_path = rtrim(exec("which pylint 2>/dev/null"));
|
||||
if (is_executable($system_path)) {
|
||||
return $system_path;
|
||||
}
|
||||
|
||||
echo "Running pip3 install to install developer dependencies.\n";
|
||||
passthru("pip3 install $exec"); // probably wrong in other cases...
|
||||
|
||||
if (is_executable($path)) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
echo "\nRunning installing deps with pip3 failed.\n You should try running 'pip3 install -r requirements.txt' by hand\n";
|
||||
echo "You can find more info at http://docs.librenms.org/Developing/Validating-Code/\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a list of exclude arguments from an array
|
||||
*
|
||||
* @param string $exclude_string such as "--exclude"
|
||||
* @param array $excludes array of directories to exclude
|
||||
* @return string resulting string
|
||||
*/
|
||||
private function buildPhpLintExcludes($exclude_string, $excludes)
|
||||
{
|
||||
$result = '';
|
||||
foreach ($excludes as $exclude) {
|
||||
$result .= $exclude_string . $exclude . ' ';
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
137
LibreNMS/Util/FileCategorizer.php
Normal file
137
LibreNMS/Util/FileCategorizer.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
/**
|
||||
* FileCategorizer.php
|
||||
*
|
||||
* Categorizes files in LibreNMS
|
||||
*
|
||||
* 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 2020 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Util;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class FileCategorizer extends Categorizer
|
||||
{
|
||||
private const TESTS_REGEX = '#^tests/(snmpsim|data)/(([0-9a-z\-]+)(_[0-9a-z\-]+)?)(_[0-9a-z\-]+)?\.(json|snmprec)$#';
|
||||
|
||||
public function __construct($items = [])
|
||||
{
|
||||
parent::__construct($items);
|
||||
|
||||
if (getenv('CIHELPER_DEBUG')) {
|
||||
$this->setSkippable(function ($item) {
|
||||
return in_array($item, ['.travis.yml', 'LibreNMS/Util/CiHelper.php', 'LibreNMS/Util/FileCategorizer.php']);
|
||||
});
|
||||
}
|
||||
|
||||
$this->addCategory('php', function ($item) {
|
||||
return Str::endsWith($item, '.php') ? $item : false;
|
||||
});
|
||||
$this->addCategory('docs', function ($item) {
|
||||
return (Str::startsWith($item, 'doc/') || $item == 'mkdocs.yml') ? $item : false;
|
||||
});
|
||||
$this->addCategory('python', function ($item) {
|
||||
return Str::endsWith($item, '.py') ? $item : false;
|
||||
});
|
||||
$this->addCategory('bash', function ($item) {
|
||||
return Str::endsWith($item, '.sh') ? $item : false;
|
||||
});
|
||||
$this->addCategory('svg', function ($item) {
|
||||
return Str::endsWith($item, '.svg') ? $item : false;
|
||||
});
|
||||
$this->addCategory('resources', function ($item) {
|
||||
return Str::startsWith($item, 'resources/') ? $item : false;
|
||||
});
|
||||
$this->addCategory('full-checks', function ($item) {
|
||||
return in_array($item, ['composer.lock', '.travis.yml']) ? $item : false;
|
||||
});
|
||||
$this->addCategory('os-files', function ($item) {
|
||||
if (($os_name = $this->osFromFile($item)) !== null) {
|
||||
return ['os' => $os_name, 'file' => $item];
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public function categorize()
|
||||
{
|
||||
parent::categorize();
|
||||
|
||||
// split out os
|
||||
$this->categorized['os'] = array_unique(array_column($this->categorized['os-files'], 'os'));
|
||||
$this->categorized['os-files'] = array_column($this->categorized['os-files'], 'file');
|
||||
|
||||
// If we have more than 4 (arbitrary number) of OS' then blank them out
|
||||
// Unit tests may take longer to run in a loop so fall back to all.
|
||||
if (count($this->categorized['os']) > 4) {
|
||||
$this->categorized['full-checks'] = [true];
|
||||
}
|
||||
|
||||
return $this->categorized;
|
||||
}
|
||||
|
||||
private function validateOs($os)
|
||||
{
|
||||
return file_exists("includes/definitions/$os.yaml") ? $os : null;
|
||||
}
|
||||
|
||||
private function osFromFile($file)
|
||||
{
|
||||
if (Str::startsWith($file, 'includes/definitions/')) {
|
||||
return basename($file, '.yaml');
|
||||
} elseif (Str::startsWith($file, ['includes/polling', 'includes/discovery'])) {
|
||||
return $this->validateOs(basename($file, '.inc.php'));
|
||||
} elseif (preg_match('#LibreNMS/OS/[^/]+.php#', $file)) {
|
||||
return $this->osFromClass(basename($file, '.php'));
|
||||
} elseif (preg_match(self::TESTS_REGEX, $file, $matches)) {
|
||||
if ($this->validateOs($matches[3])) {
|
||||
return $matches[3];
|
||||
}
|
||||
if ($this->validateOs($matches[2])) {
|
||||
return $matches[2];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* convert class name to os name
|
||||
*
|
||||
* @param string $class
|
||||
* @return string|null
|
||||
*/
|
||||
private function osFromClass($class)
|
||||
{
|
||||
preg_match_all("/[A-Z][a-z0-9]*/", $class, $segments);
|
||||
$osname = implode('-', array_map('strtolower', $segments[0]));
|
||||
$osname = preg_replace(
|
||||
['/^zero-/', '/^one-/', '/^two-/', '/^three-/', '/^four-/', '/^five-/', '/^six-/', '/^seven-/', '/^eight-/', '/^nine-/',],
|
||||
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
|
||||
$osname
|
||||
);
|
||||
|
||||
if ($os = $this->validateOs($osname)) {
|
||||
return $os;
|
||||
}
|
||||
return $this->validateOs(str_replace('-', '_', $osname));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user