%PDF- %PDF-
| Direktori : /var/www/projetos/suporte.iigd.com.br/src/Console/Database/ |
| Current File : /var/www/projetos/suporte.iigd.com.br/src/Console/Database/UpdateCommand.php |
<?php
/**
* ---------------------------------------------------------------------
*
* GLPI - Gestionnaire Libre de Parc Informatique
*
* http://glpi-project.org
*
* @copyright 2015-2024 Teclib' and contributors.
* @copyright 2003-2014 by the INDEPNET Development Team.
* @licence https://www.gnu.org/licenses/gpl-3.0.html
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* ---------------------------------------------------------------------
*/
namespace Glpi\Console\Database;
use Glpi\Cache\CacheManager;
use Glpi\Console\AbstractCommand;
use Glpi\Console\Command\ConfigurationCommandInterface;
use Glpi\Console\Command\ForceNoPluginsOptionCommandInterface;
use Glpi\Console\Traits\TelemetryActivationTrait;
use Glpi\System\Diagnostic\DatabaseSchemaIntegrityChecker;
use Glpi\Toolbox\DatabaseSchema;
use Glpi\Toolbox\VersionParser;
use GLPIKey;
use Migration;
use Session;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Update;
class UpdateCommand extends AbstractCommand implements ConfigurationCommandInterface, ForceNoPluginsOptionCommandInterface
{
use TelemetryActivationTrait;
/**
* Error code returned when trying to update from an unstable version.
*
* @var integer
*/
const ERROR_NO_UNSTABLE_UPDATE = 1;
/**
* Error code returned when security key file is missing.
*
* @var integer
*/
const ERROR_MISSING_SECURITY_KEY_FILE = 2;
/**
* Error code returned when database is not a valid GLPI database.
*
* @var integer
*/
const ERROR_INVALID_DATABASE = 3;
/**
* Error code returned when database integrity check failed.
*
* @var integer
*/
const ERROR_DATABASE_INTEGRITY_CHECK_FAILED = 4;
protected $requires_db_up_to_date = false;
protected function configure()
{
parent::configure();
$this->setName('database:update');
$this->setAliases(['db:update']);
$this->setDescription(__('Update database schema to new version'));
$this->addOption(
'allow-unstable',
'u',
InputOption::VALUE_NONE,
__('Allow update to an unstable version')
);
$this->addOption(
'--skip-db-checks',
's',
InputOption::VALUE_NONE,
__('Do not check database schema integrity before and after performing the update')
);
$this->addOption(
'force',
'f',
InputOption::VALUE_NONE,
__('Force execution of update from v-1 version of GLPI even if schema did not changed')
);
$this->registerTelemetryActivationOptions($this->getDefinition());
}
protected function initialize(InputInterface $input, OutputInterface $output)
{
/** @var \Psr\SimpleCache\CacheInterface $GLPI_CACHE */
global $GLPI_CACHE;
$GLPI_CACHE = (new CacheManager())->getInstallerCacheInstance(); // Use dedicated "installer" cache
parent::initialize($input, $output);
$this->outputWarningOnMissingOptionnalRequirements();
$this->db->disableTableCaching();
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$allow_unstable = $input->getOption('allow-unstable');
$force = $input->getOption('force');
$no_interaction = $input->getOption('no-interaction'); // Base symfony/console option
$update = new Update($this->db);
// Initialize entities
$_SESSION['glpidefault_entity'] = 0;
Session::initEntityProfiles(2);
Session::changeProfile(4);
// Display current/future state information
$currents = $update->getCurrents();
$current_version = $currents['version'];
$current_db_version = $currents['dbversion'];
if ($current_version === null) {
$msg = sprintf(
__('Current GLPI version not found for database named "%s". Update cannot be done.'),
$this->db->dbdefault
);
$output->writeln('<error>' . $msg . '</error>');
return self::ERROR_INVALID_DATABASE;
}
$informations = new Table($output);
$informations->setHeaders(['', __('Current'), _n('Target', 'Targets', 1)]);
$informations->addRow([__('Database host'), $this->db->dbhost, '']);
$informations->addRow([__('Database name'), $this->db->dbdefault, '']);
$informations->addRow([__('Database user'), $this->db->dbuser, '']);
$informations->addRow([__('GLPI version'), $current_version, GLPI_VERSION]);
$informations->addRow([
__('GLPI database version'),
$this->getPrettyDbVersion($current_db_version),
$this->getPrettyDbVersion(GLPI_SCHEMA_VERSION),
]);
$informations->render();
if (Update::isDbUpToDate() && !$force) {
$output->writeln('<info>' . __('No migration needed.') . '</info>');
return 0;
}
if (VersionParser::isStableRelease($current_version) && !VersionParser::isStableRelease(GLPI_VERSION) && !$allow_unstable) {
// Prevent unstable update unless explicitly asked
$output->writeln(
sprintf(
'<error>' . __('%s is not a stable release. Please upgrade manually or add --allow-unstable option.') . '</error>',
GLPI_VERSION
),
OutputInterface::VERBOSITY_QUIET
);
return self::ERROR_NO_UNSTABLE_UPDATE;
}
if ($update->isExpectedSecurityKeyFileMissing()) {
$output->writeln(
sprintf(
'<error>' . __('The key file "%s" used to encrypt/decrypt sensitive data is missing. You should retrieve it from your previous installation or encrypted data will be unreadable.') . '</error>',
$update->getExpectedSecurityKeyFilePath()
),
OutputInterface::VERBOSITY_QUIET
);
if ($no_interaction) {
return self::ERROR_MISSING_SECURITY_KEY_FILE;
}
}
$this->checkSchemaIntegrity($current_db_version);
$this->askForConfirmation();
/** @var \Migration $migration */
global $migration; // Migration scripts are using global `$migration`
$migration = new Migration(GLPI_VERSION);
$migration->setOutputHandler($output);
$update->setMigration($migration);
$update->doUpdates($current_version, $force);
(new CacheManager())->resetAllCaches(); // Ensure cache will not use obsolete data
$output->writeln('<info>' . __('Migration done.') . '</info>');
$this->handTelemetryActivation($input, $output);
if ($this->input->getOption('skip-db-checks')) {
$this->output->writeln(
[
'<comment>' . __('The database schema integrity check has been skipped.') . '</comment>',
'<comment>' . sprintf(
__('It is recommended to run the "%s" command to validate that the database schema is consistent with the current GLPI version.'),
'php bin/console database:check_schema_integrity'
) . '</comment>'
],
OutputInterface::VERBOSITY_QUIET
);
} elseif (!$update->isUpdatedSchemaConsistent()) {
// Exit with an error if database schema is not consistent.
// Keep this code at end of command to ensure that the whole migration is still executed.
// Many old GLPI instances will likely have differences, and people will have to fix them manually.
//
// Exiting with an exit code will permit to warn non-interactive automated scripts about the failure.
// Administrators will likely receive a failure notification and will be able to do manual actions in order to
// fix schema integrity.
$this->output->writeln(
[
'<error>' . __('The database schema is not consistent with the current GLPI version.') . '</error>',
'<error>' . sprintf(
__('It is recommended to run the "%s" command to see the differences.'),
'php bin/console database:check_schema_integrity'
) . '</error>'
],
OutputInterface::VERBOSITY_QUIET
);
return self::ERROR_DATABASE_INTEGRITY_CHECK_FAILED;
}
return 0; // Success
}
public function getNoPluginsOptionValue()
{
return true;
}
/**
* Check schema integrity of installed database.
*
* @param string $installed_version
*
* @return void
*/
private function checkSchemaIntegrity(string $installed_version): void
{
if ($this->input->getOption('skip-db-checks')) {
return;
}
$this->output->writeln('<comment>' . __('Checking database schema integrity...') . '</comment>');
$checker = new DatabaseSchemaIntegrityChecker($this->db, false, true, true, true, true, true);
if (!$checker->canCheckIntegrity($installed_version)) {
$msg = sprintf(
__('Database schema integrity check skipped as version "%s" is not supported by checking process.'),
$installed_version
);
$this->output->writeln('<comment>' . $msg . '</comment>', OutputInterface::VERBOSITY_QUIET);
return;
}
$error = null;
try {
$differences = $checker->checkCompleteSchemaForVersion($installed_version, true);
if (count($differences) > 0) {
$install_version_nohash = preg_replace('/@.+$/', '', $installed_version);
$error = sprintf(__('The database schema is not consistent with the installed GLPI version (%s).'), $install_version_nohash)
. ' '
. sprintf(__('Run the "%1$s" command to view found differences.'), 'php bin/console database:check_schema_integrity');
}
} catch (\Throwable $e) {
$error = sprintf(__('Database integrity check failed with error (%s).'), $e->getMessage());
}
if ($error !== null) {
if (!$this->input->getOption('no-interaction')) {
// On interactive mode, display error only.
// User will be asked for confirmation before update execution.
$this->output->writeln('<error>' . $error . '</error>', OutputInterface::VERBOSITY_QUIET);
} else {
// On non-interactive mode, exit with error.
throw new \Glpi\Console\Exception\EarlyExitException(
'<error>' . $error . '</error>',
self::ERROR_DATABASE_INTEGRITY_CHECK_FAILED
);
}
} else {
$this->output->writeln('<info>' . __('Database schema is OK.') . '</info>');
}
}
/**
* Get DB version to display.
*
* @param string $raw_version
*
* @return string
*/
private function getPrettyDbVersion(string $raw_version): string
{
$version_matches = [];
if (preg_match('/^(?<version>.+)@(?<hash>.+)$/', $raw_version, $version_matches) !== 1) {
// Version does not match expected pattern. It either contains no hash, either has an unexpected format.
// Preserve raw version string for debug purpose.
return $raw_version;
}
$version_cleaned = $version_matches['version'];
$version_hash = $version_matches['hash'];
if (!VersionParser::isStableRelease($version_cleaned)) {
// Not a stable version. Keep hash for debug purpose.
return $raw_version;
}
$schema_path = DatabaseSchema::getEmptySchemaPath($version_cleaned);
if ($schema_path === null || $version_hash !== sha1_file($schema_path)) {
// Version hash does not match schema file sha1. Installation was probably made from a specific commit
// or a nightly build.
// Keep hash for debug purpose.
return $raw_version;
}
return $version_cleaned;
}
public function getConfigurationFilesToUpdate(InputInterface $input): array
{
if (!(new GLPIKey())->keyExists()) {
return ['glpicrypt.key'];
}
return [];
}
}