Fixes for installer/modules race condition (#883)

This commit is contained in:
Nabeel S
2020-10-21 13:28:54 -04:00
committed by GitHub
parent 716ba38b6d
commit cd18442207
9 changed files with 135 additions and 31 deletions

View File

@@ -2,8 +2,10 @@
namespace App\Contracts; namespace App\Contracts;
use App\Models\Module;
use App\Support\Database; use App\Support\Database;
use DB; use Exception;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
@@ -27,6 +29,29 @@ abstract class Migration extends \Illuminate\Database\Migrations\Migration
{ {
} }
/**
* Add a module and enable it
*
* @param array $attrs
*/
public function addModule(array $attrs)
{
$module = array_merge([
'enabled' => true,
'created_at' => DB::raw('NOW()'),
'updated_at' => DB::raw('NOW()'),
], $attrs);
try {
DB::table('modules')->insert($module);
} catch (Exception $e) {
// setting already exists, just ignore it
if ($e->getCode() === 23000) {
return;
}
}
}
/** /**
* Seed a YAML file into the database * Seed a YAML file into the database
* *
@@ -37,7 +62,7 @@ abstract class Migration extends \Illuminate\Database\Migrations\Migration
try { try {
$path = base_path($file); $path = base_path($file);
Database::seed_from_yaml_file($path, false); Database::seed_from_yaml_file($path, false);
} catch (\Exception $e) { } catch (Exception $e) {
Log::error('Unable to load '.$file.' file'); Log::error('Unable to load '.$file.' file');
Log::error($e); Log::error($e);
} }
@@ -54,7 +79,7 @@ abstract class Migration extends \Illuminate\Database\Migrations\Migration
foreach ($rows as $row) { foreach ($rows as $row) {
try { try {
DB::table($table)->insert($row); DB::table($table)->insert($row);
} catch (\Exception $e) { } catch (Exception $e) {
// setting already exists, just ignore it // setting already exists, just ignore it
if ($e->getCode() === 23000) { if ($e->getCode() === 23000) {
continue; continue;

View File

@@ -1,6 +1,6 @@
<?php <?php
use Illuminate\Database\Migrations\Migration; use App\Contracts\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
@@ -19,6 +19,12 @@ class CreateModulesTable extends Migration
$table->boolean('enabled')->default(1); $table->boolean('enabled')->default(1);
$table->timestamps(); $table->timestamps();
}); });
$this->addModule(['name' => 'Awards']);
$this->addModule(['name' => 'Sample']);
$this->addModule(['name' => 'VMSAcars']);
$this->addModule(['name' => 'Vacentral']);
$this->addModule(['name' => 'TestModule']);
} }
/** /**

View File

@@ -1,15 +1,21 @@
<?php <?php
use App\Services\Installer\MigrationService;
use App\Services\Installer\SeederService; use App\Services\Installer\SeederService;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder class DatabaseSeeder extends Seeder
{ {
private $seederService; /** @var MigrationService */
private $migrationSvc;
/** @var SeederService */
private $seederSvc;
public function __construct() public function __construct()
{ {
$this->seederService = app(SeederService::class); $this->migrationSvc = app(MigrationService::class);
$this->seederSvc = app(SeederService::class);
} }
/** /**
@@ -19,6 +25,12 @@ class DatabaseSeeder extends Seeder
*/ */
public function run() public function run()
{ {
$this->seederService->syncAllSeeds(); // Make sure any migrations that need to be run are run/cleared out
if ($this->migrationSvc->migrationsAvailable()) {
$this->migrationSvc->runAllMigrations();
}
// Then sync all of the seeds
$this->seederSvc->syncAllSeeds();
} }
} }

View File

@@ -3,9 +3,13 @@
namespace App\Models; namespace App\Models;
use App\Contracts\Model; use App\Contracts\Model;
use Carbon\Carbon;
/** /**
* Class ModuleManager * @property string name
* @property bool enabled
* @property Carbon created_at
* @property Carbon updated_at
*/ */
class Module extends Model class Module extends Model
{ {

View File

@@ -47,6 +47,7 @@ class DatabaseService extends Service
} catch (\PDOException $e) { } catch (\PDOException $e) {
throw $e; throw $e;
} }
return true; return true;
} }

View File

@@ -3,13 +3,16 @@
namespace App\Services\Installer; namespace App\Services\Installer;
use App\Contracts\Service; use App\Contracts\Service;
use Exception;
use Illuminate\Database\Migrations\Migrator;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Log;
use Nwidart\Modules\Facades\Module; use Nwidart\Modules\Facades\Module;
class MigrationService extends Service class MigrationService extends Service
{ {
protected function getMigrator() protected function getMigrator(): Migrator
{ {
$m = app('migrator'); $m = app('migrator');
$m->setConnection(config('database.default')); $m->setConnection(config('database.default'));
@@ -36,8 +39,6 @@ class MigrationService extends Service
} }
} }
// Log::info('Update - migration paths', $paths);
return $paths; return $paths;
} }
@@ -49,10 +50,25 @@ class MigrationService extends Service
$migrator = $this->getMigrator(); $migrator = $this->getMigrator();
$migration_dirs = $this->getMigrationPaths(); $migration_dirs = $this->getMigrationPaths();
$files = $migrator->getMigrationFiles(array_values($migration_dirs)); $availMigrations = [];
$availMigrations = array_diff(array_keys($files), $migrator->getRepository()->getRan()); $runFiles = [];
// Log::info('Migrations available:', $availMigrations); try {
$runFiles = $migrator->getRepository()->getRan();
} catch (Exception $e) {
} // Skip database run initialized
$files = $migrator->getMigrationFiles(array_values($migration_dirs));
foreach ($files as $filename => $filepath) {
if (in_array($filename, $runFiles, true)) {
continue;
}
$availMigrations[] = $filepath;
}
Log::info('Migrations available:', $availMigrations);
return $availMigrations; return $availMigrations;
} }
@@ -63,11 +79,23 @@ class MigrationService extends Service
*/ */
public function runAllMigrations() public function runAllMigrations()
{ {
// A little ugly, run the main migration first, this makes sure the migration table is there
$output = ''; $output = '';
Artisan::call('migrate'); Artisan::call('migrate');
$output .= trim(Artisan::output()); $output .= trim(Artisan::output());
return $output; // Then get any remaining migrations that are left over
// Due to caching or whatever reason, the migrations are not all loaded when Artisan first
// runs. This is likely a side effect of the database being used as the module activator,
// and the list of migrations being pulled before the initial modules are populated
$migrator = $this->getMigrator();
$availMigrations = $this->migrationsAvailable();
Log::info('Running '.count($availMigrations).' available migrations');
$ret = $migrator->run($availMigrations);
Log::info('Ran '.count($ret).' migrations');
return $output."\n".implode("\n", $ret);
} }
} }

View File

@@ -56,10 +56,10 @@ class SeederService extends Service
*/ */
public function syncAllSeeds(): void public function syncAllSeeds(): void
{ {
$this->syncAllYamlFileSeeds();
$this->syncAllSettings(); $this->syncAllSettings();
$this->syncAllPermissions(); $this->syncAllPermissions();
$this->syncAllModules(); $this->syncAllModules();
$this->syncAllYamlFileSeeds();
} }
/** /**

View File

@@ -125,8 +125,12 @@ class ModuleService extends Service
'name' => $module_name, 'name' => $module_name,
'enabled' => 1, 'enabled' => 1,
]); ]);
Artisan::call('module:migrate '.$module_name);
return true; return true;
} }
return false; return false;
} }
@@ -240,6 +244,11 @@ class ModuleService extends Service
$module->update([ $module->update([
'enabled' => $status, 'enabled' => $status,
]); ]);
if ($status === true) {
Artisan::call('module:migrate '.$module->name);
}
return true; return true;
} }

View File

@@ -53,6 +53,20 @@ class DatabaseActivator implements ActivatorInterface
$this->path = $path; $this->path = $path;
} }
/**
* @param string $name
*
* @return \App\Models\Module|null
*/
public function getModuleByName(string $name): ?\App\Models\Module
{
try {
return \App\Models\Module::where(['name' => $name])->first();
} catch (Exception $e) { // Catch any database/connection errors
return null;
}
}
/** /**
* Get modules statuses, from the database * Get modules statuses, from the database
* *
@@ -66,6 +80,7 @@ class DatabaseActivator implements ActivatorInterface
foreach ($modules as $i) { foreach ($modules as $i) {
$retVal[$i->name] = $i->enabled; $retVal[$i->name] = $i->enabled;
} }
return $retVal; return $retVal;
} catch (Exception $e) { } catch (Exception $e) {
return []; return [];
@@ -85,7 +100,7 @@ class DatabaseActivator implements ActivatorInterface
*/ */
public function enable(Module $module): void public function enable(Module $module): void
{ {
$this->setActiveByName($module->getName(), true); $this->setActive($module, true);
} }
/** /**
@@ -93,7 +108,7 @@ class DatabaseActivator implements ActivatorInterface
*/ */
public function disable(Module $module): void public function disable(Module $module): void
{ {
$this->setActiveByName($module->getName(), false); $this->setActive($module, false);
} }
/** /**
@@ -102,15 +117,12 @@ class DatabaseActivator implements ActivatorInterface
*/ */
public function hasStatus(Module $module, bool $status): bool public function hasStatus(Module $module, bool $status): bool
{ {
try { $module = $this->getModuleByName($module->getName());
$module = (new \App\Models\Module())->where('name', $module->getName()); if (!$module) {
if ($module->exists()) {
return $module->first()->enabled == 1;
}
return false;
} catch (Exception $e) {
return false; return false;
} }
return $module->enabled;
} }
/** /**
@@ -118,7 +130,13 @@ class DatabaseActivator implements ActivatorInterface
*/ */
public function setActive(Module $module, bool $active): void public function setActive(Module $module, bool $active): void
{ {
$this->setActiveByName($module->getName(), $active); $module = $this->getModuleByName($module->getName());
if (!$module) {
return;
}
$module->enabled = $active;
$module->save();
} }
/** /**
@@ -126,12 +144,13 @@ class DatabaseActivator implements ActivatorInterface
*/ */
public function setActiveByName(string $name, bool $status): void public function setActiveByName(string $name, bool $status): void
{ {
$module = (new \App\Models\Module())->where('name', $name); $module = $this->getModuleByName($name);
if ($module->exists()) { if (!$module) {
$module->update([ return;
'status' => $status,
]);
} }
$module->enabled = $status;
$module->save();
} }
/** /**