Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

create tao migrations script #2409

Merged
merged 91 commits into from
Jun 22, 2020
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
a51b8ff
create tao migrations script
Feb 25, 2020
68471d2
change taoUpdate script
Feb 27, 2020
047ff90
add doctrine to composer json
Feb 27, 2020
8324a83
add phpdoc
Feb 27, 2020
88f72d9
reverting migrations
Feb 28, 2020
9992fd1
Merge branch 'develop' of https://github.com/oat-sa/tao-core into TDR…
Mar 12, 2020
2dfe4e6
change updater script
Mar 12, 2020
cecd995
remove redindant files
Mar 12, 2020
73ae505
Merge branch 'develop' of https://github.com/oat-sa/tao-core into TDR…
Mar 23, 2020
241e744
put commands in constants
Mar 23, 2020
08c3e67
generate version number after checking extension
Mar 23, 2020
fc249cd
implement apply/revoke single migration
Mar 24, 2020
0b5b17b
reduce complexity of Migrations::run() method
Mar 24, 2020
31ccc88
Merge branch 'develop' of https://github.com/oat-sa/tao-core into TDR…
Apr 16, 2020
3f7a027
bump version
Apr 16, 2020
625acc5
reduce scripts/tools/Migrations.php compexity
Apr 16, 2020
2ba22af
catch NoMigrationsToExecute
Apr 17, 2020
b6ada6e
inject service manager to migrations
Apr 17, 2020
04d9feb
remove unused statement
Apr 17, 2020
bee5709
Skip migrations after extension install
Apr 20, 2020
5865bef
skip migrations after extesions install
Apr 20, 2020
2ce546b
merge develop
Apr 20, 2020
510c1c6
skip generis migrations after tao install
Apr 20, 2020
af25ede
codeclimate changes
Apr 20, 2020
bcd4c7e
merge develop
Apr 22, 2020
0ac5c38
upgrade to doctrine migrations v3
Apr 22, 2020
3a2257d
update dependencies in composer.json
Apr 22, 2020
35f028c
fix composer.json
Apr 22, 2020
a7d36b7
codeclimate fixes
Apr 22, 2020
c81e977
update jenkinsfile
Apr 22, 2020
6bf1bba
update jenkinsfile
Apr 23, 2020
346a20f
add FE build to jenkinsfile
Apr 23, 2020
20ee6f5
Change composer discard-changes option
Apr 23, 2020
7b9c255
remove missed dependency checker
Apr 23, 2020
06d58f5
add use statements for command classes
Apr 23, 2020
1a62139
remove generis from composer.json
Apr 27, 2020
81d9f86
Merge branch 'develop' of https://github.com/oat-sa/tao-core into TDR…
Apr 27, 2020
f65538b
add generis to composer.json
Apr 27, 2020
60a555a
merge develop
May 6, 2020
a711875
change generis dependency
May 6, 2020
ac32cb9
create migrations directory
May 7, 2020
5a621de
merge develop
May 7, 2020
58dda0a
shrink file size
May 7, 2020
70e59cd
use migration to register event listener
May 8, 2020
a884298
merge develop
May 8, 2020
88d2f0c
add test case of TaoComparator and TaoClassNameGenerator
May 8, 2020
4affa0f
merge develop
May 8, 2020
58a930e
make changes according to reviewer comments
May 13, 2020
2db74b5
configure persistence id for migration service
May 14, 2020
32171e9
fix return types according to PSR-12
May 14, 2020
7ac53a3
Bump version 42.9.0
bugalot May 15, 2020
4a364a0
Merge branch 'develop' into TDR-21/migrations_tool
bugalot May 15, 2020
d08bc8f
remove generis version from composer.json
May 15, 2020
be33468
remove generis version from composer.json
May 15, 2020
420177c
remove unused use statements; do not use __DIR__ constant; add compor…
May 18, 2020
a9ae090
add UpdateExtensionsTest unit test
May 18, 2020
c894350
Merge branch 'develop' into TDR-21/migrations_tool
bugalot May 19, 2020
cec8f56
add logger interface to migration class
May 20, 2020
2f9e932
Merge branch 'TDR-21/migrations_tool' of https://github.com/oat-sa/ta…
May 20, 2020
7904aa2
Merge branch 'develop' into TDR-21/migrations_tool
bugalot May 22, 2020
a7dc333
Update scripts/install/RegisterEvents.php
May 25, 2020
9bb7288
revert jenkinsfile; remove generis dependency
May 25, 2020
29de1dd
Merge branch 'develop' into TDR-21/migrations_tool
bugalot May 31, 2020
500c571
Use Doctrine Migrations beta1
bugalot Jun 2, 2020
d92a374
Class name change for class being extended by TaoMigrationRepository.php
bugalot Jun 2, 2020
c3b0027
Get rid of extending @internal class!
bugalot Jun 3, 2020
f17ff93
Removing useless class
bugalot Jun 3, 2020
859d844
Merge branch 'develop' into TDR-21/migrations_tool
bugalot Jun 3, 2020
d6d65b5
Merge branch 'TDR-21/migrations_tool' into TDR-21/migrations_tool_mig…
bugalot Jun 3, 2020
dbf9731
Merge branch 'develop' into TDR-21/migrations_tool
bugalot Jun 8, 2020
4d244ae
Merge branch 'develop' into TDR-21/migrations_tool_migrations300-beta1
bugalot Jun 8, 2020
f8344ea
Merge branch 'TDR-21/migrations_tool' into TDR-21/migrations_tool_mig…
bugalot Jun 8, 2020
50bda9c
Add reporting capabilities.
bugalot Jun 8, 2020
e96ff96
Some PHP Documentation
bugalot Jun 8, 2020
6ac37b2
Add report capability and unit tests.
bugalot Jun 8, 2020
024f900
More detailed unit tests
bugalot Jun 8, 2020
1df19f9
Remove color usage
bugalot Jun 8, 2020
2ec1251
No end of line in new unit test.
bugalot Jun 8, 2020
13576af
No end of line
bugalot Jun 8, 2020
6c3fa48
Mock Reports
bugalot Jun 9, 2020
57f308b
Change Mock to Stub for terminology
bugalot Jun 9, 2020
b3081b4
Stubs are Mocks?
bugalot Jun 9, 2020
e24f23d
Merge branch 'develop' into TDR-21/migrations_tool
bugalot Jun 12, 2020
544f9d0
Merge branch 'TDR-21/migrations_tool' into TDR-21/migrations_tool_mig…
bugalot Jun 12, 2020
73e65d3
Go for 3.0.0 stable
bugalot Jun 14, 2020
253c69f
Merge pull request #2528 from oat-sa/TDR-21/migrations_tool_migration…
bugalot Jun 15, 2020
2a891fc
Merge branch 'develop' into TDR-21/migrations_tool
bugalot Jun 18, 2020
eeac1b1
merge develop
Jun 19, 2020
5d1d21a
add quotes to the version in the migration generation command output
Jun 19, 2020
d197497
Merge branch 'develop' of https://github.com/oat-sa/tao-core into TDR…
Jun 22, 2020
dbeacff
merge develop
Jun 22, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
"imsglobal/lti": "^3.0",
"paragonie/random_compat": "^2.0",
"oat-sa/composer-npm-bridge": "^0.3.0",
"symfony/dotenv": "~4.3"
"symfony/dotenv": "~4.3",
"doctrine/migrations" : "^2.0"
hutnikau marked this conversation as resolved.
Show resolved Hide resolved
},
"autoload": {
"psr-4": {
Expand Down
2 changes: 1 addition & 1 deletion manifest.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
'label' => 'TAO Base',
'description' => 'TAO meta-extension',
'license' => 'GPL-2.0',
'version' => '41.14.0',
'version' => '41.15.0',
'author' => 'Open Assessment Technologies, CRP Henri Tudor',
'requires' => [
'generis' => '>=12.19.0',
Expand Down
15 changes: 11 additions & 4 deletions models/classes/extension/UpdateExtensions.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use oat\oatbox\log\LoggerAggregator;
use oat\oatbox\service\ServiceNotFoundException;
use oat\tao\model\asset\AssetService;
use oat\tao\scripts\tools\Migrations;

/**
* Extends the generis updater to take into account
Expand All @@ -50,17 +51,23 @@ public function __invoke($params)
}
$report = parent::__invoke($params);

// regenrate locals
$migrations = new Migrations();
hutnikau marked this conversation as resolved.
Show resolved Hide resolved
$migrations->setServiceLocator($this->getServiceLocator());
$migrationsReport = $migrations->__invoke(['-c', 'migrate']);

$report->add($migrationsReport);

// regenerate locales
$files = \tao_models_classes_LanguageService::singleton()->generateAll();
if (count($files) > 0) {
$report->add(new common_report_Report(common_report_Report::TYPE_SUCCESS, __('Successfully updated %s client translation bundles', count($files))));
} else {
$report->add(new common_report_Report(common_report_Report::TYPE_ERROR, __('No client translation bundles updated')));
}

$updateid = $this->generateUpdateId();
$this->updateCacheBuster($report, $updateid);
$report->add(new common_report_Report(common_report_Report::TYPE_INFO, __('Update ID : %s', $updateid)));
$updateId = $this->generateUpdateId();
$this->updateCacheBuster($report, $updateId);
$report->add(new common_report_Report(common_report_Report::TYPE_INFO, __('Update ID : %s', $updateId)));

return $report;
}
Expand Down
326 changes: 326 additions & 0 deletions scripts/tools/Migrations.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,326 @@
<?php

namespace oat\tao\scripts\tools;

use Doctrine\DBAL\Connection;
use oat\tao\scripts\tools\migrations\Configuration;
use Doctrine\Migrations\Tools\Console\Command;
hutnikau marked this conversation as resolved.
Show resolved Hide resolved
use Doctrine\Migrations\Tools\Console\Helper\ConfigurationHelper;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\QuestionHelper;
use oat\generis\persistence\PersistenceManager;
use oat\oatbox\extension\script\ScriptAction;
use oat\oatbox\extension\script\ScriptException;
use common_ext_Extension;
use oat\tao\scripts\tools\migrations\TaoFinder;
use common_report_Report as Report;
use Doctrine\Migrations\Exception\MigrationException;
use Doctrine\Migrations\Exception\NoMigrationsToExecute;

/**
* Class Migrations
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say that having a more detailed description of the class would be nice for comprehension.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

* Usage examples:
* ```
* //generate new migration class
* sudo -u www-data php index.php '\oat\tao\scripts\tools\Migrations' -c generate -e taoAct
* //show migrations status
* sudo -u www-data php index.php '\oat\tao\scripts\tools\Migrations' -c status
* //apply all migrations
* sudo -u www-data php index.php '\oat\tao\scripts\tools\Migrations' -c migrate
* //migrate to version
* sudo -u www-data php index.php '\oat\tao\scripts\tools\Migrations' -c migrate -v 202003120846502234_tao
* ```
* @package oat\tao\scripts\tools
*/
class Migrations extends ScriptAction
hutnikau marked this conversation as resolved.
Show resolved Hide resolved
{

protected const MIGRATIONS_DIR = 'migrations';
protected const COMMAND_GENERATE = 'generate';
protected const COMMAND_STATUS = 'status';
protected const COMMAND_MIGRATE = 'migrate';
protected const COMMAND_EXECUTE = 'execute';
protected const COMMAND_ROLLBACK = 'rollback';

private $commands = [
self::COMMAND_GENERATE => 'migrations:generate',
self::COMMAND_STATUS => 'migrations:status',
self::COMMAND_MIGRATE => 'migrations:migrate',
self::COMMAND_EXECUTE => 'migrations:execute',
self::COMMAND_ROLLBACK => 'migrations:execute',
];

protected function provideOptions()
{
return [
'command' => [
'prefix' => 'c',
'longPrefix' => 'command',
'required' => true,
'description' => 'Command to be run'
],
'extension' => [
'prefix' => 'e',
'longPrefix' => 'extension',
'required' => false,
'description' => 'Extension for which migration needs to be generated'
],
'version' => [
'prefix' => 'v',
'longPrefix' => 'version',
'required' => false,
'description' => 'Version number to migrate'
],
];
}

/**
* @return string
*/
protected function provideDescription()
{
return 'Tao migrations tool';
}

/**
* @return Report
* @throws MigrationException
* @throws ScriptException
* @throws \common_ext_ExtensionException
*/
public function run()
{
$command = $this->getOption('command');

if (!isset($this->commands[$command])) {
throw new ScriptException(sprintf('Command "%s" is not supported', $command));
}

return $this->doCommand($command);
}

/**
* @param string $command
* @return Report
* @throws MigrationException
* @throws ScriptException
* @throws \common_ext_ExtensionException
*/
private function doCommand($command)
{
$output = $this->{$command}();
return new Report(Report::TYPE_INFO, $output->fetch());
}

/**
* @return BufferedOutput
* @throws MigrationException
* @throws ScriptException
* @throws \common_ext_ExtensionException
*/
private function generate()
hutnikau marked this conversation as resolved.
Show resolved Hide resolved
{
if (!$this->hasOption('extension')) {
throw new ScriptException('extension option missed');
}

$extension = $this->getExtension();
$input = ['command' => $this->commands[self::COMMAND_GENERATE]];
$configuration = $this->getConfiguration();
$configuration->setExtension($extension);
$configuration->setMigrationsDirectory($extension->getDir().self::MIGRATIONS_DIR);
$configuration->setMigrationsNamespace($this->getExtensionNamespace($extension));
$configuration->setCustomTemplate(
__DIR__.DIRECTORY_SEPARATOR.'migrations'.DIRECTORY_SEPARATOR.'Template.tpl'
);
$connection = $this->getConnection();
$helperSet = new HelperSet();
$helperSet->set(new QuestionHelper(), 'question');
$helperSet->set(new ConfigurationHelper($connection, $configuration));
$this->executeMigration($helperSet, new ArrayInput($input), $output = new BufferedOutput());

return $output;
}

/**
* @return BufferedOutput
* @throws MigrationException
* @throws ScriptException
*/
private function status()
{
$input = new ArrayInput(['command' => $this->commands[self::COMMAND_STATUS]]);
$this->executeMigration(new HelperSet(), $input, $output = new BufferedOutput());
return $output;
}

/**
* @param bool $rollback
* @return BufferedOutput
* @throws MigrationException
* @throws ScriptException
*/
private function execute(bool $rollback = false)
{
$input = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The command building part could be a good candidate for decoupling. It's being spread all across this class and spoiling its internal interface while introducing other code smells (control flag parameters, magic constants).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the only responsibility of this class is to build command as it is only wrapper of doctrine migration tool.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does more than composing migration commands:

  • gives a CLI API by inheriting ScriptAction and deals with actual execution
  • generates migration files from template
  • executes and manages migrations
  • initializes the meta storage
  • rollbacks migrations
  • provides status on migrations
  • configures the underlying Doctrine migrations layer
  • initializes the DB connection
  • resolves extensions
  • defines the extension namespace

All these to be addressed in this PR is not my attention, rather to raise your awareness as there's lot more here than command-wrapping. These could be tackled step by step later on. The mindset here is to make the code better piece by piece, iteration by iteration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this actions are done by doctrine migration tool.
Two responsibilities of this class is to configure doctrine migration tool and proxy commands to it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Half of these (2,3,4,5,6). And we have an agreement plus I resolve this conversation :).

'command' => $this->commands[self::COMMAND_EXECUTE],
];
if ($this->hasOption('version')) {
$input['version'] = $this->getOption('version');
}
if ($rollback) {
$input['--down'] = true;
} else {
$input['--up'] = true;
}

$input[] = '--no-interaction';
$this->executeMigration(new HelperSet(), new ArrayInput($input), $output = new BufferedOutput());
return $output;
}

/**
* @return BufferedOutput
* @throws MigrationException
* @throws ScriptException
*/
private function rollback()
{
return $this->execute(true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's no other way but a control flag, resolving this magic constant will greatly increase one's understanding on this part.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually IDE does the trick:
image
But i can move it to the constant if you prefer

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I see only the value, I have no clue about what it's actually doing :(

And this is the first thing I turn off in my storm :)

}

/**
* @return BufferedOutput
* @throws MigrationException
* @throws ScriptException
*/
private function migrate()
hutnikau marked this conversation as resolved.
Show resolved Hide resolved
{
$input = [
'command' => $this->commands[self::COMMAND_MIGRATE],
];

if ($this->hasOption('version')) {
$input['version'] = $this->getOption('version');
}

$input[] = '--no-interaction';
$this->executeMigration(new HelperSet(), new ArrayInput($input), $output = new BufferedOutput());
return $output;
}

/**
* @param HelperSet $helperSet
* @param InputInterface $input
* @param OutputInterface|null $output
* @throws MigrationException
* @throws ScriptException
*/
private function executeMigration(HelperSet $helperSet, InputInterface $input, OutputInterface $output = null)
{
$helperSet->set(new QuestionHelper(), 'question');

if (!$helperSet->has('configuration')) {
$connection = $this->getConnection();
$configuration = $this->getConfiguration();
$helperSet->set(new ConfigurationHelper($connection, $configuration));
}

$cli = new Application('Doctrine Migrations');
$cli->setAutoExit(false);
$cli->setCatchExceptions(true);
$cli->setHelperSet($helperSet);
$cli->setCatchExceptions(false);
$cli->addCommands(array(
new migrations\commands\GenerateCommand(),
new Command\MigrateCommand(),
new Command\StatusCommand(),
new Command\ExecuteCommand(),
//new Command\DumpSchemaCommand(),
hutnikau marked this conversation as resolved.
Show resolved Hide resolved
//new Command\LatestCommand(),
//new Command\RollupCommand(),
//new Command\VersionCommand()
));
try {
$cli->run($input, $output);
} catch (NoMigrationsToExecute $e) {
$output->write($e->getMessage());
} catch (\Exception $e) {
$this->logWarning('Migration error: ' . $e->getMessage());
throw new ScriptException('Migration error: ' . $e->getMessage());
}
}

/**
* @return Configuration
* @throws MigrationException
*/
private function getConfiguration()
{
$connection = $this->getConnection();
$configuration = new Configuration($connection);
$configuration->setServiceLocator($this->getServiceLocator());
$configuration->setName('Tao Migrations');
$configuration->setMigrationsTableName('doctrine_migration_versions');
$configuration->setMigrationsColumnName('version');
$configuration->setMigrationsColumnLength(255);
$configuration->setMigrationsExecutedAtColumnName('executed_at');
$configuration->setAllOrNothing(true);
$configuration->setCheckDatabasePlatform(false);
$configuration->setMigrationsDirectory(ROOT_PATH);
$configuration->setMigrationsFinder(new TaoFinder(ROOT_PATH));
$configuration->setMigrationsNamespace('oat');

return $configuration;
}

/**
* @return Connection
*/
private function getConnection()
{
/** @var PersistenceManager $persistenceManager */
$persistenceManager = $this->getServiceLocator()->get(PersistenceManager::SERVICE_ID);
//todo: use migrations service to store persistence id in it's option
$persistence = $persistenceManager->getPersistenceById('default');
gitromba marked this conversation as resolved.
Show resolved Hide resolved
return $persistence->getDriver()->getDbalConnection();
}

/**
* @return common_ext_Extension
* @throws ScriptException
*/
private function getExtension()
{
$extensionId = $this->getOption('extension');
/** @var \common_ext_ExtensionsManager $extensionManager */
$extensionManager = $this->getServiceLocator()->get(\common_ext_ExtensionsManager::SERVICE_ID);

if (!$extensionManager->isInstalled($extensionId)) {
throw new ScriptException(sprintf('Extension "%s" is not installed', $extensionId));
}

try {
return $extensionManager->getExtensionById($extensionId);
} catch (\common_ext_ExtensionException $e) {
$this->logWarning('Error during extension retrieval: '.$e->getMessage());
throw new ScriptException(sprintf('Cannot retrieve extension "%s"', $extensionId));
}
}

/**
* This is an assumption
* @param common_ext_Extension $extension
* @return string
*/
private function getExtensionNamespace(common_ext_Extension $extension)
{
return 'oat\\'.$extension->getId().'\\migrations';
hutnikau marked this conversation as resolved.
Show resolved Hide resolved
}
}

Loading