Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
!/.gitignore
!/app
!/bin
!/config/settings.yaml.sample
!/composer.json
!/composer.lock
!/README.md
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@

## :eye: Installation

1) Set database variables in app/Settings.php
1) Run following command to install dependencies: `composer install`

2) Run the following command to init the interactive utility:
2) Configure SSH and database settings
- Configuration files lives in: `/config`
- Each project should have it's own directory. Example: `/config/Project`
- Inside each project's directory there should be a `settings.yaml` file
- Structure should look like: `/config/project/settings.yaml`
- `/config/settings.yaml.sample` should be used as skeleton

3) Run the following command to init the interactive utility:
```
$ bin/console compare
$ bin/console compare -p <projectname>
```

# Send us your feedback!
Expand Down
271 changes: 209 additions & 62 deletions app/CompareCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,112 +6,259 @@

namespace Console\App\Commands;

use Console\App\Commands\Settings;
use Console\App\Commands\CoreConfigData;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
// use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Question\Question;

class CompareCommand extends Command
{
protected function configure()
{
$this->setName('compare')
->setDescription('Compare contents of a database table for two Magento instances');
// ->addArgument('username', InputArgument::REQUIRED, 'Pass some argument.');
->setDescription('Compare contents of a database table for two Magento instances')
->addOption('project', 'p', InputOption::VALUE_REQUIRED, 'Specify project');
}

protected function execute(InputInterface $input, OutputInterface $output)
{
// $input->getArgument('username')
// Get project option
$project = strtolower($input->getOption('project'));

// Start
Settings::connectToDb();
$io = new SymfonyStyle($input, $output);
if (empty($project)) {
$io->error('Project must be specified. Example: bin/console compare -p project');
return;
}

$io->title('Database comparison');
$io->text('Connecting to '.ucfirst($project));

// Open SSH Tunnels
try {
$sshTunnels = new Tunnel();
/** @var Tunnel $openedTunnels */
$openedTunnels = $sshTunnels
->setProject($project)
->openTunnels();
} catch (\Exception $e) {
$io->error($e->getMessage());

return;
}

// Error occurred when opening SSH tunnels
if ($openedTunnels->getHasError()) {
$io->error($openedTunnels->getErrorMessage());

return;
}

// Connect to DB
Settings::connectToDb($project);

// Test connection
// $this->testConnection($io);
try {
if (!$this->testConnection($io, $project)) {
$openedTunnels->closeTunnels();
}
} catch (\Exception $e) {
$openedTunnels->closeTunnels();
$io->error($e->getMessage());

return;
}

// Show Compare Options
$this->comparisionTool($openedTunnels, $io, $input, $output);
}

/**
* Comparision tool
* @param $openedTunnels Tunnel
* @param $io
* @param $input
* @param $output
* @throws \Exception
*/
protected function comparisionTool($openedTunnels, $io, $input, $output)
{
// DB config
$dbConfig = Settings::getYamlConfig($openedTunnels->getProject(), 'databases');

// Select mode
$selectedMode = $io->choice('Select mode to compare', [
'Configurations (core_config_data)',
'Another table'
$selectedMode = $io->choice('Select tool', [
'Config by path (core_config_data)',
'Exit'
]);

// Process selection
switch ($selectedMode) :
case 'Configurations (core_config_data)' :

$dataDb1 = CoreConfigData::on(Settings::DB1_NAME)
->select('config_id', 'scope_id', 'path', 'value')
->get();

$dataDb2 = CoreConfigData::on(Settings::DB2_NAME)
->select('config_id', 'scope_id', 'path', 'value')
->get();

// Select direction
$selectedDirection = $io->choice('Select direction to compare', [
Settings::DB1_NAME . ' => ' . Settings::DB2_NAME,
Settings::DB2_NAME . ' => ' . Settings::DB1_NAME
]);

// Process data
if ($selectedDirection == (Settings::DB1_NAME . ' => ' . Settings::DB2_NAME)) {
$primaryDb = Settings::DB1_NAME;
$secondaryDb = Settings::DB2_NAME;
$headers = ['path', 'scope_id', Settings::DB1_NAME, Settings::DB2_NAME];
$finalArray = CoreConfigData::compareConfigurations($dataDb1, $dataDb2);
case 'Config by path (core_config_data)' :

// Set table headers
$headers = ['config_id', 'scope', 'scope_id', $dbConfig['db1']['alias'], $dbConfig['db2']['alias']];

// Select path
$helper = $this->getHelper('question');
$question = new Question('Enter path: ', 'web/unsecure/base_url');
$path = $helper->ask($input, $output, $question);
if (!$path) return;

// Get values
$dataDb1 = $this->getConfigByDb($dbConfig['db1']['database'], $path);
$dataDb2 = $this->getConfigByDb($dbConfig['db2']['database'], $path);

// Render table
if(!count($dataDb1) && !count($dataDb2)) {
$io->note('No records found with path "'.$path.'"');
} else {
$primaryDb = Settings::DB2_NAME;
$secondaryDb = Settings::DB1_NAME;
$headers = ['path', 'scope_id', Settings::DB2_NAME, Settings::DB1_NAME];
$finalArray = CoreConfigData::compareConfigurations($dataDb2, $dataDb1);
$configsAll = $this->populateConfigTable($dataDb1, $dataDb2);
$this->renderConfigTable($headers, $configsAll, $output, $io);
}

$io->listing([
'Showing values that are different',
'Showing values that are present in [' . $primaryDb . '] but not in [' . $secondaryDb . ']',
'Values are truncated if its length is greater than ' . Settings::MAX_VALUE_LENGTH
]);

// Show table
$table = new Table($output);
$table->setHeaders($headers)
->setRows($finalArray);
$table->render();
break;

case 'Another table' :
$io->text('TO-DO');
$io->newLine();
break;
case 'Exit' :
$this->closeConnection($openedTunnels, $io);
exit;
endswitch;
}

// Show Compare Options
$this->comparisionTool($openedTunnels, $io, $input, $output);
}

/**
* Test connection by getting Base URL
*/
private function testConnection($io)
private function testConnection($io, $project)
{
$config = CoreConfigData::on(Settings::DB1_NAME)
$dbConfig = Settings::getYamlConfig($project, 'databases');

$config1 = CoreConfigData::on($dbConfig['db1']['database'])
->where('path', 'web/unsecure/base_url')
->get()
->first();

$config2 = CoreConfigData::on($dbConfig['db2']['database'])
->where('path', 'web/unsecure/base_url')
->get()
->first();

if (isset($config->path)) {
$io->success('Connection establish a successfully');
$io->text($config->path . ' => ' . $config->value);
if (isset($config1->path) && isset($config2->path)) {
$io->success('Connection established successfully');
$io->newLine();
} else {
$io->error('Couldn\'t establish a connection');

return false;
}

return true;
}

/**
* Close SSH Tunnels
* @param $openedTunnels
* @param $io
*/
protected function closeConnection($openedTunnels, $io)
{
$io->newLine();
try {
$openedTunnels->closeTunnels();
$io->text('Connection closed');
$io->newLine();
} catch (\Exception $e) {
$io->error($e->getMessage());

return;
}
}

/**
* Get config value by database/path
* @param $database
* @param $path
* @return \Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection
*/
protected function getConfigByDb($database, $path)
{
return CoreConfigData::on($database)
->select('config_id', 'scope', 'scope_id', 'path', 'value')
->where('path', '=', $path)
->get();
}

/**
* Populate table with core_config_data values
* @param $dataDb1
* @param $dataDb2
* @return array
*/
protected function populateConfigTable($dataDb1, $dataDb2)
{
$configsAll = [];
$configs2 = [];

exit;
if(count($dataDb1)) {
foreach ($dataDb1 as $key => $config1) {
$configsAll[ $key ][] = $config1->config_id;
$configsAll[ $key ][] = $config1->scope;
$configsAll[ $key ][] = $config1->scope_id;
$configsAll[ $key ][] = $this->formatValue($config1->value);
foreach ($dataDb2 as $key => $config2) {
if ($config1->scope == $config2->scope && $config1->scope_id == $config2->scope_id) {
$configs2[] = $config2->config_id;
$configsAll[ $key ][] = $this->formatValue($config2->value);
}
}
}
}

if(count($dataDb2)) {
foreach ($dataDb2 as $key => $config2) {
if (!in_array($config2->config_id, $configs2)) {
$configsAll[ $key ][] = $config2->config_id;
$configsAll[ $key ][] = $config2->scope;
$configsAll[ $key ][] = $config2->scope_id;
$configsAll[ $key ][] = '';
$configsAll[ $key ][] = $this->formatValue($config2->value);
}
}
}

return $configsAll;
}

/**
* Render table with core_config_data values
* @param $headers
* @param $configsAll
* @param $output
* @param $io
*/
protected function renderConfigTable($headers, $configsAll, $output, $io)
{
$table = new Table($output);
$table->setHeaders($headers)
->setRows($configsAll);
$table->render();
$io->newLine();
}

/**
* Format value
* @param $value
* @param int $len
* @return bool|string
*/
protected static function formatValue($value, $len = Settings::MAX_VALUE_LENGTH)
{
return strlen($value) > $len ?
substr($value, 0, $len) . '...' :
$value;
}
}
}
Loading