Cache version data (#14404)

* Cache version data
Calling cli commands can be expensive, cache the results.

* style fixes

* Fix pre-laravel usage

* fix lint
This commit is contained in:
Tony Murray 2022-09-28 23:23:32 -05:00
parent 9648edcf1b
commit 06c361c2b5
No known key found for this signature in database
GPG Key ID: E57F02A35A950069
4 changed files with 147 additions and 96 deletions

View File

@ -26,6 +26,7 @@
namespace LibreNMS\Util;
use DB;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
@ -38,19 +39,16 @@ class Version
// Update this on release
public const VERSION = '22.9.0';
/**
* @var bool
*/
protected $is_git_install = false;
public function __construct()
{
$this->is_git_install = Git::repoPresent() && Git::binaryExists();
}
/** @var array */
protected $cache = [];
public static function get(): Version
{
return new static;
try {
return app()->make('version');
} catch (BindingResolutionException $e) {
return new static; // no container, just return a fresh instance
}
}
public function release(): string
@ -60,11 +58,23 @@ class Version
public function local(): string
{
if ($this->is_git_install && $version = $this->fromGit()) {
return $version;
}
return $this->cacheGet('local_version', function () {
if ($this->isGitInstall()) {
$version = rtrim(shell_exec('git describe --tags 2>/dev/null'));
if ($version) {
return $version;
}
}
return self::VERSION;
return self::VERSION;
});
}
public function isGitInstall(): bool
{
return $this->cacheGet('install_type', function () {
return (Git::repoPresent() && Git::binaryExists()) ? 'git' : 'other';
}) == 'git';
}
/**
@ -74,31 +84,43 @@ class Version
*/
public function localCommit(): array
{
if ($this->is_git_install) {
$install_dir = base_path();
$version_process = new Process(['git', 'show', '--quiet', '--pretty=%H|%ct'], $install_dir);
$version_process->run();
return [
'sha' => $this->localCommitSha(),
'date' => $this->localDate(),
'branch' => $this->localBranch(),
];
}
// failed due to permissions issue
if ($version_process->getExitCode() == 128 && Str::startsWith($version_process->getErrorOutput(), 'fatal: unsafe repository')) {
(new Process(['git', 'config', '--global', '--add', 'safe.directory', $install_dir]))->run();
$version_process->run();
public function localCommitSha(): string
{
return $this->cacheGet('local_commit_sha', function () {
if (! $this->isGitInstall()) {
return '';
}
[$local_sha, $local_date] = array_pad(explode('|', rtrim($version_process->getOutput())), 2, '');
return $this->localCommitData()[0] ?? '';
});
}
$branch_process = new Process(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], $install_dir);
public function localDate(): string
{
return $this->cacheGet('local_commit_date', function () {
return $this->localCommitData()[1] ?? '';
});
}
public function localBranch(): string
{
return $this->cacheGet('local_branch', function () {
if (! $this->isGitInstall()) {
return '';
}
$branch_process = new Process(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], Config::get('install_dir'));
$branch_process->run();
$branch = rtrim($branch_process->getOutput());
return [
'sha' => $local_sha,
'date' => $local_date,
'branch' => $branch,
];
}
return ['sha' => null, 'date' => null, 'branch' => null];
return rtrim($branch_process->getOutput());
});
}
/**
@ -108,16 +130,16 @@ class Version
*/
public function remoteCommit(): array
{
if ($this->is_git_install && Config::get('update_channel') == 'master') {
try {
$github = \Http::withOptions(['proxy' => Proxy::forGuzzle()])->get(Config::get('github_api') . 'commits/master');
return $github->json();
} catch (ConnectionException $e) {
return json_decode($this->cacheGet('remote_commit', function () {
if ($this->isGitInstall()) {
try {
return \Http::withOptions(['proxy' => Proxy::forGuzzle()])->get(Config::get('github_api') . 'commits/master')->body();
} catch (ConnectionException $e) {
}
}
}
return [];
return '[]';
}), true);
}
public function databaseServer(): string
@ -156,53 +178,50 @@ class Version
return ['last' => 'Not Connected', 'total' => 0];
}
private function fromGit(): string
{
return rtrim(shell_exec('git describe --tags 2>/dev/null'));
}
public function gitChangelog(): string
{
return $this->is_git_install
? rtrim(shell_exec('git log -10'))
: '';
}
public function gitDate(): string
{
return $this->is_git_install
? rtrim(shell_exec("git show --pretty='%ct' -s HEAD"))
: '';
return $this->cacheGet('changelog', function () {
return $this->isGitInstall()
? rtrim(shell_exec('git log -10'))
: '';
});
}
public function python(): string
{
$proc = new Process(['python3', '--version']);
$proc->run();
return $this->cacheGet('python', function () {
$proc = new Process(['python3', '--version']);
$proc->run();
if ($proc->getExitCode() !== 0) {
return '';
}
if ($proc->getExitCode() !== 0) {
return '';
}
return explode(' ', rtrim($proc->getOutput()), 2)[1] ?? '';
return explode(' ', rtrim($proc->getOutput()), 2)[1] ?? '';
});
}
public function rrdtool(): string
{
$process = new Process([Config::get('rrdtool', 'rrdtool'), '--version']);
$process->run();
preg_match('/^RRDtool ([\w.]+) /', $process->getOutput(), $matches);
return $this->cacheGet('rrdtool', function () {
$process = new Process([Config::get('rrdtool', 'rrdtool'), '--version']);
$process->run();
preg_match('/^RRDtool ([\w.]+) /', $process->getOutput(), $matches);
return str_replace('1.7.01.7.0', '1.7.0', $matches[1] ?? '');
return str_replace('1.7.01.7.0', '1.7.0', $matches[1] ?? '');
});
}
public function netSnmp(): string
{
$process = new Process([Config::get('snmpget', 'snmpget'), '-V']);
$process->run();
preg_match('/[\w.]+$/', $process->getErrorOutput(), $matches);
return $this->cacheGet('net-snmp', function () {
$process = new Process([Config::get('snmpget', 'snmpget'), '-V']);
return $matches[0] ?? '';
$process->run();
preg_match('/[\w.]+$/', $process->getErrorOutput(), $matches);
return $matches[0] ?? '';
});
}
/**
@ -210,30 +229,61 @@ class Version
*/
public function os(): string
{
$info = [];
return $this->cacheGet('os', function () {
$info = [];
// find release file
if (file_exists('/etc/os-release')) {
$info = @parse_ini_file('/etc/os-release');
} else {
foreach (glob('/etc/*-release') as $file) {
$content = file_get_contents($file);
// normal os release style
$info = @parse_ini_string($content);
if (! empty($info)) {
break;
}
// find release file
if (file_exists('/etc/os-release')) {
$info = @parse_ini_file('/etc/os-release');
} else {
foreach (glob('/etc/*-release') as $file) {
$content = file_get_contents($file);
// normal os release style
$info = @parse_ini_string($content);
if (! empty($info)) {
break;
}
// just a string of text
if (substr_count($content, PHP_EOL) <= 1) {
$info = ['NAME' => trim(str_replace('release ', '', $content))];
break;
// just a string of text
if (substr_count($content, PHP_EOL) <= 1) {
$info = ['NAME' => trim(str_replace('release ', '', $content))];
break;
}
}
}
$only = array_intersect_key($info, ['NAME' => true, 'VERSION_ID' => true]);
return implode(' ', $only);
});
}
/**
* We want these each runtime, so don't use the global cache
*/
private function cacheGet(string $name, callable $actual): string
{
if (! array_key_exists($name, $this->cache)) {
$this->cache[$name] = $actual($name);
}
$only = array_intersect_key($info, ['NAME' => true, 'VERSION_ID' => true]);
return $this->cache[$name];
}
return implode(' ', $only);
private function localCommitData(): array
{
return explode('|', $this->cacheGet('local_commit_data', function () {
$install_dir = Config::get('install_dir');
$version_process = new Process(['git', 'show', '--quiet', '--pretty=%H|%ct'], $install_dir);
$version_process->run();
// failed due to permissions issue
if ($version_process->getExitCode() == 128 && Str::startsWith($version_process->getErrorOutput(), 'fatal: unsafe repository')) {
(new Process(['git', 'config', '--global', '--add', 'safe.directory', $install_dir]))->run();
$version_process->run();
}
return rtrim($version_process->getOutput());
}));
}
}

View File

@ -68,7 +68,7 @@ class AboutController extends Controller
'db_schema' => vsprintf('%s (%s)', $version->database()),
'git_log' => $version->gitChangelog(),
'git_date' => $version->gitDate(),
'git_date' => $version->localDate(),
'project_name' => Config::get('project_name'),
'version_local' => $version->local(),

View File

@ -31,6 +31,9 @@ class AppServiceProvider extends ServiceProvider
$this->app->singleton('device-cache', function ($app) {
return new \LibreNMS\Cache\Device();
});
$this->app->singleton('version', function ($app) {
return new \LibreNMS\Util\Version();
});
$this->app->bind(\App\Models\Device::class, function () {
/** @var \LibreNMS\Cache\Device $cache */

View File

@ -541,12 +541,12 @@ function parse_location($location)
function version_info($remote = false)
{
$version = \LibreNMS\Util\Version::get();
$local = $version->localCommit();
$output = [
return [
'local_ver' => $version->local(),
'local_sha' => $local['sha'],
'local_date' => $local['date'],
'local_branch' => $local['branch'],
'local_sha' => $version->localCommitSha(),
'local_date' => $version->localDate(),
'local_branch' => $version->localBranch(),
'github' => $remote ? $version->remoteCommit() : null,
'db_schema' => vsprintf('%s (%s)', $version->database()),
'php_ver' => phpversion(),
@ -555,8 +555,6 @@ function version_info($remote = false)
'rrdtool_ver' => $version->rrdtool(),
'netsnmp_ver' => $version->netSnmp(),
];
return $output;
}//end version_info()
/**