Skip to content

Commit

Permalink
fix: propagate -a flag to subprocesses
Browse files Browse the repository at this point in the history
  • Loading branch information
gggeek committed Jan 16, 2019
1 parent 4094e2d commit 57d19c8
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 91 deletions.
5 changes: 3 additions & 2 deletions API/StorageHandlerInterface.php
Expand Up @@ -45,10 +45,11 @@ public function addMigration(MigrationDefinition $migrationDefinition);
* Starts a migration (creates and stores it, in STARTED status)
*
* @param MigrationDefinition $migrationDefinition
* @param bool $force
* @return Migration
* @throws \Exception If the migration was already executed, skipped or executing
* @throws \Exception If the migration was already executing. Also if it as already (executed|skipped) unless $force is true
*/
public function startMigration(MigrationDefinition $migrationDefinition);
public function startMigration(MigrationDefinition $migrationDefinition, $force = false);

/**
* Ends a migration (updates it)
Expand Down
31 changes: 25 additions & 6 deletions Command/GenerateCommand.php
Expand Up @@ -33,9 +33,10 @@ protected function configure()
->addOption('match-type', null, InputOption::VALUE_REQUIRED, 'The type of identifier used to find the entity to generate the migration for', null)
->addOption('match-value', null, InputOption::VALUE_REQUIRED, 'The identifier value used to find the entity to generate the migration for. Can have many values separated by commas', null)
->addOption('match-except', null, InputOption::VALUE_NONE, 'Used to match all entities except the ones satisfying the match-value condition', null)
->addOption('lang', null, InputOption::VALUE_REQUIRED, 'The language of the migration (eng-GB, ger-DE, ...)', 'eng-GB')
->addOption('lang', 'l', InputOption::VALUE_REQUIRED, 'The language of the migration (eng-GB, ger-DE, ...). If null, the default language of the current siteaccess is used')
->addOption('dbserver', null, InputOption::VALUE_REQUIRED, 'The type of the database server the sql migration is for, when type=db (mysql, postgresql, ...)', 'mysql')
->addOption('role', null, InputOption::VALUE_REQUIRED, 'Deprecated: The role identifier (or id) that you would like to update, for type=role', null)
->addOption('admin-login', 'a', InputOption::VALUE_REQUIRED, "Login of admin account used whenever elevated privileges are needed (user id 14 used by default)")
->addArgument('bundle', InputArgument::REQUIRED, 'The bundle to generate the migration definition file in. eg.: AcmeMigrationBundle')
->addArgument('name', InputArgument::OPTIONAL, 'The migration name (will be prefixed with current date)', null)
->setHelp(<<<EOT
Expand Down Expand Up @@ -156,7 +157,8 @@ public function execute(InputInterface $input, OutputInterface $output)
'matchValue' => $matchValue,
'matchExcept' => $matchExcept,
'mode' => $mode,
'lang' => $input->getOption('lang')
'lang' => $input->getOption('lang'),
'adminLogin' => $input->getOption('admin-login')
);

$date = date('YmdHis');
Expand Down Expand Up @@ -207,6 +209,7 @@ public function execute(InputInterface $input, OutputInterface $output)

/**
* Generates a migration definition file.
* @todo allow non-filesystem storage
*
* @param string $path filename to file to generate (full path)
* @param string $fileType The type of migration file to generate
Expand Down Expand Up @@ -241,10 +244,7 @@ protected function generateMigrationFile($path, $fileType, $migrationType, array
}
$executor = $this->getMigrationService()->getExecutor($migrationType);

$context = array();
if (isset($parameters['lang']) && $parameters['lang'] != '') {
$context['defaultLanguageCode'] = $parameters['lang'];
}
$context = $this->migrationContextFromParameters($parameters);

$matchCondition = array($parameters['matchType'] => $parameters['matchValue']);
if ($parameters['matchExcept']) {
Expand Down Expand Up @@ -312,4 +312,23 @@ protected function getGeneratingExecutors()
}
return $executors;
}

/**
* @see MigrationService::migrationContextFromParameters
* @param array $parameters
* @return array
*/
protected function migrationContextFromParameters(array $parameters)
{
$context = array();

if (isset($parameters['lang']) && $parameters['lang'] != '') {
$context['defaultLanguageCode'] = $parameters['lang'];
}
if (isset($parameters['adminLogin']) && $parameters['adminLogin'] != '') {
$context['adminUserLogin'] = $parameters['adminLogin'];
}

return $context;
}
}
94 changes: 51 additions & 43 deletions Command/MassMigrateCommand.php
Expand Up @@ -89,41 +89,17 @@ protected function execute(InputInterface $input, OutputInterface $output)
}

$concurrency = $input->getOption('concurrency');
$this->writeln("Executing migrations using ".count($paths)." processes with a concurrency of $concurrency");
$this->writeln("Executing migrations using " . count($paths) . " processes with a concurrency of $concurrency");

$kernel = $this->getContainer()->get('kernel');
$builder = new ProcessBuilder();
$executableFinder = new PhpExecutableFinder();
if (false !== ($php = $executableFinder->find())) {
$builder->setPrefix($php);
}

// mandatory args and options
$builderArgs = array(
$_SERVER['argv'][0], // sf console
self::COMMAND_NAME, // name of sf command. Can we get it from the Application instead of hardcoding?
'--env=' . $kernel-> getEnvironment(), // sf env
'--child'
);
// 'optional' options
// note: options 'clear-cache' we never propagate
if (!$kernel->isDebug()) {
$builderArgs[] = '--no-debug';
}
if ($input->getOption('default-language')) {
$builderArgs[]='--default-language='.$input->getOption('default-language');
}
if ($input->getOption('no-transactions')) {
$builderArgs[]='--no-transactions';
}
if ($input->getOption('siteaccess')) {
$builderArgs[]='--siteaccess='.$input->getOption('siteaccess');
}
if ($input->getOption('ignore-failures')) {
$builderArgs[]='--ignore-failures';
}
if ($input->getOption('separate-process')) {
$builderArgs[]='--separate-process';
}
$builderArgs = $this->createChildProcessArgs($input);

$processes = array();
/** @var MigrationDefinition $migrationDefinition */
foreach($paths as $path => $count) {
Expand Down Expand Up @@ -181,21 +157,8 @@ protected function execute(InputInterface $input, OutputInterface $output)
if (false !== $php = $executableFinder->find()) {
$builder->setPrefix($php);
}
// mandatory args and options
$builderArgs = array(
$_SERVER['argv'][0], // sf console
MigrateCommand::COMMAND_NAME, // name of sf command
'--env=' . $this->getContainer()->get('kernel')->getEnvironment(), // sf env
'--child'
);
// 'optional' options
// note: options 'clear-cache', 'ignore-failures' and 'no-transactions' we never propagate
if ($input->getOption('default-language')) {
$builderArgs[] = '--default-language=' . $input->getOption('default-language');
}
if ($input->getOption('no-transactions')) {
$builderArgs[] = '--no-transactions';
}

$builderArgs = parent::createChildProcessArgs($input);
}

$failed = 0;
Expand Down Expand Up @@ -433,4 +396,49 @@ protected function groupMigrationsByPath($toExecute)
return $paths;
}

/**
* Returns the command-line arguments needed to execute a migration in a separate subprocess (omitting 'path')
* @param InputInterface $input
* @return array
*/
protected function createChildProcessArgs(InputInterface $input)
{
$kernel = $this->getContainer()->get('kernel');

// mandatory args and options
$builderArgs = array(
$_SERVER['argv'][0], // sf console
self::COMMAND_NAME, // name of sf command. Can we get it from the Application instead of hardcoding?
'--env=' . $kernel-> getEnvironment(), // sf env
'--child'
);
// sf/ez env options
if (!$kernel->isDebug()) {
$builderArgs[] = '--no-debug';
}
if ($input->getOption('siteaccess')) {
$builderArgs[] = '--siteaccess=' . $input->getOption('siteaccess');
}
// 'optional' options
// note: options 'clear-cache', 'no-interaction', 'path' we never propagate
if ($input->getOption('admin-login')) {
$builderArgs[] = '--admin-login=' . $input->getOption('admin-login');
}
if ($input->getOption('default-language')) {
$builderArgs[] = '--default-language=' . $input->getOption('default-language');
}
if ($input->getOption('force')) {
$builderArgs[] = '--force';
}
if ($input->getOption('ignore-failures')) {
$builderArgs[] = '--ignore-failures';
}
if ($input->getOption('no-transactions')) {
$builderArgs[] = '--no-transactions';
}
if ($input->getOption('separate-process')) {
$builderArgs[] = '--separate-process';
}

}
}
81 changes: 54 additions & 27 deletions Command/MigrateCommand.php
Expand Up @@ -41,14 +41,15 @@ protected function configure()
->setName(self::COMMAND_NAME)
->setAliases(array('kaliop:migration:update'))
->setDescription('Execute available migration definitions.')
->addOption('path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, "The directory or file to load the migration definitions from")
// nb: when adding options, remember to forward them to sub-commands executed in 'separate-process' mode
->addOption('default-language', 'l', InputOption::VALUE_REQUIRED, "Default language code that will be used if no language is provided in migration steps")
->addOption('admin-login', 'a', InputOption::VALUE_REQUIRED, "Login of admin account used whenever elevated privileges are needed (user id 14 used by default)")
->addOption('ignore-failures', 'i', InputOption::VALUE_NONE, "Keep executing migrations even if one fails")
->addOption('clear-cache', 'c', InputOption::VALUE_NONE, "Clear the cache after the command finishes")
->addOption('default-language', 'l', InputOption::VALUE_REQUIRED, "Default language code that will be used if no language is provided in migration steps")
->addOption('force', 'f', InputOption::VALUE_NONE, "Force (re)execution of migrations already DONE, SKIPPED or FAILED. Use with great care!")
->addOption('ignore-failures', 'i', InputOption::VALUE_NONE, "Keep executing migrations even if one fails")
->addOption('no-interaction', 'n', InputOption::VALUE_NONE, "Do not ask any interactive question")
->addOption('no-transactions', 'u', InputOption::VALUE_NONE, "Do not use a repository transaction to wrap each migration. Unsafe, but needed for legacy slot handlers")
->addOption('path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, "The directory or file to load the migration definitions from")
->addOption('separate-process', 'p', InputOption::VALUE_NONE, "Use a separate php process to run each migration. Safe if your migration leak memory. A tad slower")
->addOption('child', null, InputOption::VALUE_NONE, "*DO NOT USE* Internal option for when forking separate processes")
->setHelp(<<<EOT
Expand Down Expand Up @@ -106,27 +107,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
if (false !== $php = $executableFinder->find()) {
$builder->setPrefix($php);
}
// mandatory args and options
$builderArgs = array(
$_SERVER['argv'][0], // sf console
self::COMMAND_NAME, // name of sf command. Can we get it from the Application instead of hardcoding?
'--env=' . $kernel->getEnvironment(), // sf env
'--child'
);
// 'optional' options
// note: options 'clear-cache', 'ignore-failures' and 'no-transactions' we never propagate
if (!$kernel->isDebug()) {
$builderArgs[] = '--no-debug';
}
if ($input->getOption('default-language')) {
$builderArgs[] = '--default-language=' . $input->getOption('default-language');
}
if ($input->getOption('no-transactions')) {
$builderArgs[] = '--no-transactions';
}
if ($input->getOption('siteaccess')) {
$builderArgs[]='--siteaccess='.$input->getOption('siteaccess');
}
$builderArgs = $this->createChildProcessArgs($input);
}

$executed = 0;
Expand Down Expand Up @@ -247,19 +228,25 @@ function($type, $buffer) {
/**
* @param string[] $paths
* @param MigrationService $migrationService
* @param bool $force when true, look not only for TODO migrations, but also DONE, SKIPPED, FAILED ones (we still omit STARTED and SUSPENDED ones)
* @return MigrationDefinition[]
*
* @todo this does not scale well with many definitions or migrations
*/
protected function buildMigrationsList($paths, $migrationService)
protected function buildMigrationsList($paths, $migrationService, $force = false)
{
$migrationDefinitions = $migrationService->getMigrationsDefinitions($paths);
$migrations = $migrationService->getMigrations();

$allowedStatuses = array(Migration::STATUS_TODO);
if ($force) {
$allowedStatuses = array_merge($allowedStatuses, array(Migration::STATUS_DONE, Migration::STATUS_FAILED, Migration::STATUS_SKIPPED));
}

// filter away all migrations except 'to do' ones
$toExecute = array();
foreach ($migrationDefinitions as $name => $migrationDefinition) {
if (!isset($migrations[$name]) || (($migration = $migrations[$name]) && $migration->status == Migration::STATUS_TODO)) {
if (!isset($migrations[$name]) || (($migration = $migrations[$name]) && in_array($migration->status, $allowedStatuses))) {
$toExecute[$name] = $migrationService->parseMigrationDefinition($migrationDefinition);
}
}
Expand All @@ -268,7 +255,7 @@ protected function buildMigrationsList($paths, $migrationService)
// found by the loader
if (empty($paths)) {
foreach ($migrations as $migration) {
if ($migration->status == Migration::STATUS_TODO && !isset($toExecute[$migration->name])) {
if (in_array($migration->status, $allowedStatuses) && !isset($toExecute[$migration->name])) {
$migrationDefinitions = $migrationService->getMigrationsDefinitions(array($migration->path));
if (count($migrationDefinitions)) {
$migrationDefinition = reset($migrationDefinitions);
Expand Down Expand Up @@ -361,4 +348,44 @@ protected function setVerbosity($verbosity)
$this->verbosity = $verbosity;
}

/**
* Returns the command-line arguments needed to execute a migration in a separate subprocess (omitting 'path')
* @param InputInterface $input
* @return array
*/
protected function createChildProcessArgs(InputInterface $input)
{
$kernel = $this->getContainer()->get('kernel');

// mandatory args and options
$builderArgs = array(
$_SERVER['argv'][0], // sf console
self::COMMAND_NAME, // name of sf command. Can we get it from the Application instead of hardcoding?
'--env=' . $kernel->getEnvironment(), // sf env
'--child'
);
// sf/ez env options
if (!$kernel->isDebug()) {
$builderArgs[] = '--no-debug';
}
if ($input->getOption('siteaccess')) {
$builderArgs[]='--siteaccess='.$input->getOption('siteaccess');
}
// 'optional' options
// note: options 'clear-cache', 'ignore-failures', 'no-interaction', 'path' and 'separate-process' we never propagate
if ($input->getOption('admin-login')) {
$builderArgs[] = '--admin-login=' . $input->getOption('admin-login');
}
if ($input->getOption('default-language')) {
$builderArgs[] = '--default-language=' . $input->getOption('default-language');
}
if ($input->getOption('force')) {
$builderArgs[] = '--force';
}
if ($input->getOption('no-transactions')) {
$builderArgs[] = '--no-transactions';
}

return $builderArgs;
}
}

0 comments on commit 57d19c8

Please sign in to comment.